[alsa-devel] [PATCH v2] Scarlett mixer interface inclusion
This is a followup to David Henningsson's PATCH [1] to include the work of Tobias Hoffman and Robin Gareus in getting the Scarlett mixer interface working. I've read through the comments and tried to include fixes where appropriate.
Overall, I'd like to get a good base commit and start working on other commits that address any major structual changes such as making functions more generic or any other cleanup.
I've tested this on my Scarlett 18i8 mixer and it does work, however there are still a few issues which need to be fixed:
1) When loading a device one gets the following messages: snd-usb-audio 1-1.1:1.0: control 2:0:0:Master Playback Switch:0 is already present snd-usb-audio: probe of 1-1.1:1.3 failed with error -5
2) When unloading a device there are numerous sysfs_remove_group issues: usb 1-1.1: USB disconnect, device number 6 ------------[ cut here ]------------ WARNING: CPU: 0 PID: 54 at /build/buildd/linux-3.16.0/fs/sysfs/group.c:219 sysfs_remove_group+0x99/0xa0() sysfs group ffffffff82cbd6e0 not found for kobject 'midi1' Modules linked in: snd_usb_audio(OE) snd_usbmidi_lib(OE) ctr ccm xt_conntrack ipt_REJECT xt_CHECKSUM iptable_mangle ipt_MASQUERADE iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack xt_tcpudp bridge stp llc iptable_filter ip_tables x_tables arc4 iwldvm snd_hda_codec_hdmi mac80211 snd_hda_codec_conexant snd_hda_codec_generic iwlwifi snd_hda_intel uvcvideo snd_hda_controller videobuf2_vmalloc snd_hda_codec videobuf2_memops videobuf2_core snd_hwdep v4l2_common snd_pcm intel_rapl x86_pkg_temp_thermal videodev intel_powerclamp media coretemp kvm_intel bnep kvm btusb rfcomm cfg80211 joydev bluetooth serio_raw 6lowpan_iphc thinkpad_acpi nvram snd_seq_midi lpc_ich snd_seq_midi_event snd_rawmidi shpchp snd_seq mei_me snd_seq_device snd_timer mei snd soundcore parport_pc ppdev mac_hid lp parport binfmt_misc nls_iso8859_1 dm_crypt crct10dif_pclmul crc32_pclmul ghash_clmulni_intel aesni_intel aes_x86_64 lrw gf128mul glue_helper ablk_helper cryptd i915 psmouse ahci libahci firewire_ohci sdhci_pci firewire_core sdhci crc_itu_t i2c_algo_bit e1000e drm_kms_helper drm ptp pps_core wmi video CPU: 0 PID: 54 Comm: khubd Tainted: G OE 3.16.0-23-generic #30-Ubuntu Hardware name: LENOVO 4177CTO/4177CTO, BIOS 83ET76WW (1.46 ) 07/05/2013 0000000000000009 ffff880407b4f920 ffffffff8277fcbc ffff880407b4f968 ffff880407b4f958 ffffffff8206fd8d 0000000000000000 ffffffff82cbd6e0 ffff880405c7b410 0000000000000001 ffff8803eb059cf8 ffff880407b4f9b8 Call Trace: [<ffffffff8277fcbc>] dump_stack+0x45/0x56 [<ffffffff8206fd8d>] warn_slowpath_common+0x7d/0xa0 [<ffffffff8206fdfc>] warn_slowpath_fmt+0x4c/0x50 [<ffffffff82256538>] ? kernfs_find_and_get_ns+0x48/0x60 [<ffffffff8225a259>] sysfs_remove_group+0x99/0xa0 [<ffffffff824d6927>] dpm_sysfs_remove+0x57/0x60 [<ffffffff824cb625>] device_del+0x45/0x1d0 [<ffffffff824cb7ce>] device_unregister+0x1e/0x70 [<ffffffff824cb89c>] device_destroy+0x3c/0x50 [<ffffffffc045f34b>] sound_remove_unit+0x5b/0x90 [soundcore] [<ffffffffc045f3aa>] unregister_sound_special+0x2a/0x30 [soundcore] [<ffffffffc04cc9b0>] snd_unregister_oss_device+0x60/0x100 [snd] [<ffffffffc0550ca9>] snd_rawmidi_dev_disconnect+0x119/0x160 [snd_rawmidi] [<ffffffffc04cc14c>] __snd_device_disconnect.part.1+0x1c/0x60 [snd] [<ffffffffc04cc437>] snd_device_disconnect_all+0x47/0x60 [snd] [<ffffffffc04c59f7>] snd_card_disconnect+0x147/0x1f0 [snd] [<ffffffffc09cf5a8>] usb_audio_disconnect+0x78/0x1a0 [snd_usb_audio] [<ffffffff825867b4>] usb_unbind_interface+0x74/0x2b0 [<ffffffff824cf76f>] __device_release_driver+0x7f/0xf0 [<ffffffff824cf803>] device_release_driver+0x23/0x30 [<ffffffff824cf0f8>] bus_remove_device+0x108/0x180 [<ffffffff824cb709>] device_del+0x129/0x1d0 [<ffffffff82583f21>] usb_disable_device+0x91/0x290 [<ffffffff82578688>] usb_disconnect+0x98/0x2f0 [<ffffffff8257a6cf>] hub_port_connect+0x7f/0xac0 [<ffffffff8257b9a3>] hub_events+0x893/0xda0 [<ffffffff8257beed>] hub_thread+0x3d/0x160 [<ffffffff820b9590>] ? prepare_to_wait_event+0x100/0x100 [<ffffffff8257beb0>] ? hub_events+0xda0/0xda0 [<ffffffff82094aeb>] kthread+0xdb/0x100 [<ffffffff82094a10>] ? kthread_create_on_node+0x1c0/0x1c0 [<ffffffff82787c3c>] ret_from_fork+0x7c/0xb0 [<ffffffff82094a10>] ? kthread_create_on_node+0x1c0/0x1c0 ---[ end trace 7a8cdc4a9c65ddf5 ]---
However this issue occured even without the patch applied.
Please follow up with additional comments and review and I'll be happy to incorporate them into the next patch version.
[1] http://mailman.alsa-project.org/pipermail/alsa-devel/2014-October/081986.htm...
Chris J Arges (1): Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
sound/usb/Makefile | 1 + sound/usb/mixer.c | 27 +- sound/usb/quirks-table.h | 51 -- sound/usb/scarlett/scarlettmixer.c | 1264 ++++++++++++++++++++++++++++++++++++ sound/usb/scarlett/scarlettmixer.h | 6 + 5 files changed, 1293 insertions(+), 56 deletions(-) create mode 100644 sound/usb/scarlett/scarlettmixer.c create mode 100644 sound/usb/scarlett/scarlettmixer.h
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. Finally the code was placed in its own subdirectory.
Author: Tobias Hoffman th55@gmx.de Author: Robin Gareus robin@gareus.org Signed-off-by: David Henningsson david.henningsson@canonical.com Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/Makefile | 1 + sound/usb/mixer.c | 27 +- sound/usb/quirks-table.h | 51 -- sound/usb/scarlett/scarlettmixer.c | 1264 ++++++++++++++++++++++++++++++++++++ sound/usb/scarlett/scarlettmixer.h | 6 + 5 files changed, 1293 insertions(+), 56 deletions(-) create mode 100644 sound/usb/scarlett/scarlettmixer.c create mode 100644 sound/usb/scarlett/scarlettmixer.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..4267e47 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -12,6 +12,7 @@ snd-usb-audio-objs := card.o \ pcm.o \ proc.o \ quirks.o \ + scarlett/scarlettmixer.o \ stream.o
snd-usbmidi-lib-objs := midi.o diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 2e4a9db..7b18e28 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -62,6 +62,7 @@ #include "helper.h" #include "mixer_quirks.h" #include "power.h" +#include "scarlett/scarlettmixer.h"
#define MAX_ID_ELEMS 256
@@ -2462,11 +2463,27 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif, break; }
- if ((err = snd_usb_mixer_controls(mixer)) < 0 || - (err = snd_usb_mixer_status_create(mixer)) < 0) - goto _error; - - snd_usb_mixer_apply_create_quirk(mixer); + switch (chip->usb_id) { + case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */ + case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */ + case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */ + case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */ + case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */ + /* don't even try to parse UAC2 descriptors */ + err = scarlett_mixer_controls(mixer); + if (err < 0) + goto _error; + break; + default: + err = snd_usb_mixer_controls(mixer); + if (err < 0) + goto _error; + err = snd_usb_mixer_status_create(mixer); + if (err < 0) + goto _error; + snd_usb_mixer_apply_create_quirk(mixer); + break; + }
err = snd_device_new(chip->card, SNDRV_DEV_CODEC, mixer, &dev_ops); if (err < 0) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 223c47b..70ae637 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2637,57 +2637,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), .type = QUIRK_MIDI_NOVATION } }, -{ - /* - * Focusrite Scarlett 18i6 - * - * Avoid mixer creation, which otherwise fails because some of - * the interface descriptor subtypes for interface 0 are - * unknown. That should be fixed or worked-around but this at - * least allows the device to be used successfully with a DAW - * and an external mixer. See comments below about other - * ignored interfaces. - */ - USB_DEVICE(0x1235, 0x8004), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Focusrite", - .product_name = "Scarlett 18i6", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Device Firmware Update) */ - .ifnum = 5, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } - } - } -},
/* Access Music devices */ { diff --git a/sound/usb/scarlett/scarlettmixer.c b/sound/usb/scarlett/scarlettmixer.c new file mode 100644 index 0000000..5ae4fc1 --- /dev/null +++ b/sound/usb/scarlett/scarlettmixer.c @@ -0,0 +1,1264 @@ +/* + * Scarlett 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> + * + * 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 and testing: + * David Henningsson <david.henningsson at canonical.com> + * Chris J Arges <chris.j.arges at canonical.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Rewritten and extended to support more models, e.g. Scarlett 18i8. + * + * Many features of Scarlett 18i6 do not lend themselves to be implemented + * as simple mixer-quirk -- or at least I don't see a way how to do that, yet. + * Hence the top parts of this file is a 1:1 copy of select static functions + * from mixer.c to implement the interface. + * Suggestions how to avoid this code duplication are very welcome. + * + * eventually this should either be integrated as quirk into mixer_quirks.c + * or become a standalone module. + * + * This source hardcodes the URBs for the Scarlett, + * 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. + * + * NB. Neither the OSX nor the win driver provided by Focusrite performs + * discovery, they seem to operate the same as this driver. + */ + +/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface. + * + * The protocol was reverse engineered by looking at communication between + * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 + * (firmware v305) using wireshark and usbmon in January 2013. + * Extended in July 2013. + * + * this mixer gives complete access to all features of the device: + * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z) + * - select clock source + * - dynamic input to mixer-matrix assignment + * - 18 x 6 mixer-matrix gain stages + * - bus routing & volume control + * - save setting to hardware + * - automatic re-initialization on connect if device was power-cycled + * - peak monitoring of all 3 buses (18 input, 6 DAW input, 6 route chanels) + * (changing the samplerate and buffersize is supported by the PCM interface) + * + * + * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR) + * wIndex + * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + + * channel, data=Line/Inst (2bytes) + * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes) + * ?? wValue=0x0803/04, ?? (2bytes) + * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes) + * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes) + * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte) + * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes) + * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes) + * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes) + * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes) + * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes) + * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff) + * + * USB reads: (i.e. actually issued by original software) + * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!) + * 0x29 wValue=0x0100 sample-rate(4bytes) + * wValue=0x0200 ?? 1byte (only once) + * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ?? + * + * USB reads with bRequest = 0x03 = UAC2_CS_MEM + * 0x3c wValue=0x0002 1byte: sync status (locked=1) + * wValue=0x0000 18*2byte: peak meter (inputs) + * wValue=0x0001 8(?)*2byte: peak meter (mix) + * wValue=0x0003 6*2byte: peak meter (pcm/daw) + * + * USB write with bRequest = 0x03 + * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5 + * + * + * <ditaa> + * /--------------\ 18chn 6chn /--------------\ + * | Hardware in +--+-------\ /------+--+ ALSA PCM out | + * --------------/ | | | | --------------/ + * | | | | + * | v v | + * | +---------------+ | + * | \ Matrix Mux / | + * | +-----+-----+ | + * | | | + * | | 18chn | + * | v | + * | +-----------+ | + * | | Mixer | | + * | | Matrix | | + * | | | | + * | | 18x6 Gain | | + * | | stages | | + * | +-----+-----+ | + * | | | + * | | | + * | 18chn | 6chn | 6chn + * v v v + * ========================= + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +-----+-----+ +-----+-----+ + * | | + * | 6chn | + * v | + * +-------------+ | + * | Master Gain | | + * +------+------+ | + * | | + * | 6chn | 18chn + * | (3 stereo pairs) | + * /--------------\ | | /--------------\ + * | Hardware out |<--/ -->| ALSA PCM in | + * --------------/ --------------/ + * </ditaa> + * + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +#include "../usbaudio.h" +#include "../mixer.h" +#include "../helper.h" +#include "../power.h" + +#include "scarlettmixer.h" + +/* some gui mixers can't handle negative ctl values */ +#define LEVEL_BIAS 128 + +struct scarlett_enum_info { + int start, len; + const char **texts; +}; + +struct scarlett_device_info { + int matrix_in; + int matrix_out; + int input_len; + int output_len; + + int pcm_start; + int analog_start; + int spdif_start; + int adat_start; + int mix_start; + + struct scarlett_enum_info opt_master; + struct scarlett_enum_info opt_matrix; + + int (*controls_fn)(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info); + + int matrix_mux_init[]; +}; + +struct scarlett_mixer_elem_info { + struct usb_mixer_interface *mixer; + + /* URB command details */ + int wValue, index; + int val_len; + + int count; /* number of channels, using ++wValue */ + + const struct scarlett_enum_info *opt; + + int cached; + int cache_val[MAX_CHANNELS]; +}; + +static void scarlett_mixer_elem_free(struct snd_kcontrol *kctl) +{ + kfree(kctl->private_data); + kctl->private_data = NULL; +} + +/***************************** Low Level USB I/O *****************************/ + +/* stripped down/adapted from get_ctl_value_v2 */ +static int get_ctl_urb2(struct snd_usb_audio *chip, + int bRequest, int wValue, int index, + unsigned char *buf, int size) +{ + int ret, idx = 0; + + ret = snd_usb_autoresume(chip); + if (ret < 0 && ret != -ENODEV) { + ret = -EIO; + goto error; + } + + down_read(&chip->shutdown_rwsem); + if (chip->shutdown) { + ret = -ENODEV; + } else { + idx = snd_usb_ctrl_intf(chip) | (index << 8); + ret = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + bRequest, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, size); + } + up_read(&chip->shutdown_rwsem); + snd_usb_autosuspend(chip); + + if (ret < 0) { +error: + dev_err(&(chip->dev->dev), + "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, size = %d\n", + bRequest, wValue, idx, size); + return ret; + } + return 0; +} + +/* adopted from snd_usb_mixer_set_ctl_value */ +static int set_ctl_urb2(struct snd_usb_audio *chip, + int request, int wValue, int index, + unsigned char *buf, int val_len) +{ + int idx = 0, err, timeout = 10; + + err = snd_usb_autoresume(chip); + if (err < 0 && err != -ENODEV) + return -EIO; + down_read(&chip->shutdown_rwsem); + while (timeout-- > 0) { + if (chip->shutdown) + break; + idx = snd_usb_ctrl_intf(chip) | (index << 8); + if (snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), request, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_OUT, + wValue, idx, buf, val_len) >= 0) { + err = 0; + goto out; + } + } + dev_err(&(chip->dev->dev), + "cannot set ctl value: req = %#x, wValue = %#x, wIndex = %#x, len = %d, data = %#x/%#x\n", + request, wValue, idx, val_len, buf[0], buf[1]); + err = -EINVAL; + + out: + up_read(&chip->shutdown_rwsem); + snd_usb_autosuspend(chip); + return err; +} + +/***************************** High Level USB *****************************/ + +static int set_ctl_value(struct scarlett_mixer_elem_info *elem, int channel, + int value) +{ + struct snd_usb_audio *chip = elem->mixer->chip; + unsigned char buf[2]; + int err; + + if (elem->val_len == 2) { /* S16 */ + buf[0] = value & 0xff; + buf[1] = (value >> 8) & 0xff; + } else { /* U8 */ + buf[0] = value & 0xff; + } + + err = set_ctl_urb2(chip, UAC2_CS_CUR, elem->wValue + channel, + elem->index, buf, elem->val_len); + if (err < 0) + return err; + + elem->cached |= 1 << channel; + elem->cache_val[channel] = value; + return 0; +} + +/* + TODO: can't read back any volume (master/mixer), only cache works [?] + [return 0xfe for enums???] +*/ +static int get_ctl_value(struct scarlett_mixer_elem_info *elem, int channel, + int *value) +{ + struct snd_usb_audio *chip = elem->mixer->chip; + unsigned char buf[2] = {0, 0}; + int err, val_len; + + if (elem->cached & (1 << channel)) { + *value = elem->cache_val[channel]; + return 0; + } + + val_len = elem->val_len; + /* quirk: write 2bytes, but read 1byte */ + if ((elem->index == 0x01) || /* input impedance and input pad switch */ + ((elem->index == 0x0a) && (elem->wValue < 0x0200)) || /* bus mutes */ + (elem->index == 0x32) || (elem->index == 0x33)) { /* mux */ + val_len = 1; + } + + err = get_ctl_urb2(chip, UAC2_CS_CUR, elem->wValue + channel, elem->index, + buf, val_len); + if (err < 0) { + dev_err(&(chip->dev->dev), + "cannot get current value for control %x ch %d: err = %d\n", + elem->wValue, channel, err); + return err; + } + + if (val_len == 2) { /* S16 */ + *value = buf[0] | ((unsigned int)buf[1] << 8); + if (*value >= 0x8000) + (*value) -= 0x10000; + } else { /* U8 */ + *value = buf[0]; + } + + elem->cached |= 1 << channel; + elem->cache_val[channel] = *value; + + return 0; +} + +/********************** Enum Strings *************************/ +static const char txtOff[] = "Off", + txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2", + txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4", + txtPcm5[] = "PCM 5", txtPcm6[] = "PCM 6", + txtPcm7[] = "PCM 7", txtPcm8[] = "PCM 8", + txtPcm9[] = "PCM 9", txtPcm10[] = "PCM 10", + txtPcm11[] = "PCM 11", txtPcm12[] = "PCM 12", + txtPcm13[] = "PCM 13", txtPcm14[] = "PCM 14", + txtPcm15[] = "PCM 15", txtPcm16[] = "PCM 16", + txtPcm17[] = "PCM 17", txtPcm18[] = "PCM 18", + txtPcm19[] = "PCM 19", txtPcm20[] = "PCM 20", + txtAnlg1[] = "Analog 1", txtAnlg2[] = "Analog 2", + txtAnlg3[] = "Analog 3", txtAnlg4[] = "Analog 4", + txtAnlg5[] = "Analog 5", txtAnlg6[] = "Analog 6", + txtAnlg7[] = "Analog 7", txtAnlg8[] = "Analog 8", + txtSpdif1[] = "SPDIF 1", txtSpdif2[] = "SPDIF 2", + txtAdat1[] = "ADAT 1", txtAdat2[] = "ADAT 2", + txtAdat3[] = "ADAT 3", txtAdat4[] = "ADAT 4", + txtAdat5[] = "ADAT 5", txtAdat6[] = "ADAT 6", + txtAdat7[] = "ADAT 7", txtAdat8[] = "ADAT 8", + txtMix1[] = "Mix A", txtMix2[] = "Mix B", + txtMix3[] = "Mix C", txtMix4[] = "Mix D", + txtMix5[] = "Mix E", txtMix6[] = "Mix F", + txtMix7[] = "Mix G", txtMix8[] = "Mix H"; + +static const struct scarlett_enum_info opt_pad = { + .start = 0, + .len = 2, + .texts = (const char *[]){ + txtOff, "-10dB" + } +}; + +static const struct scarlett_enum_info opt_impedance = { + .start = 0, + .len = 2, + .texts = (const char *[]){ + "Line", "Hi-Z" + } +}; + +static const struct scarlett_enum_info opt_clock = { + .start = 1, + .len = 3, + .texts = (const char *[]){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct scarlett_enum_info opt_sync = { + .start = 0, + .len = 2, + .texts = (const char *[]){ + "No Lock", "Locked" + } +}; + +static const struct scarlett_enum_info opt_save = { + .start = 0, + .len = 2, + .texts = (const char *[]){ + "---", "Save" + } +}; + +static int scarlett_ctl_switch_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = elem->count; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->count; i++) { + err = get_ctl_value(elem, i, &val); + if (err < 0) + return err; + + val = !val; /* alsa uses 0: on, 1: off */ + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->count; i++) { + err = get_ctl_value(elem, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = set_ctl_value(elem, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = elem->count; + uinfo->value.integer.min = -128 + LEVEL_BIAS; + uinfo->value.integer.max = (int)kctl->private_value + LEVEL_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->count; i++) { + err = get_ctl_value(elem, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + LEVEL_BIAS; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->count; i++) { + err = get_ctl_value(elem, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = set_ctl_value(elem, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = elem->count; + uinfo->value.enumerated.items = elem->opt->len; + if (uinfo->value.enumerated.item > uinfo->value.enumerated.items - 1) + uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1; + strcpy(uinfo->value.enumerated.name, elem->opt->texts[uinfo->value.enumerated.item]); + return 0; +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + int err, val; + + err = get_ctl_value(elem, 0, &val); + if (err < 0) + return err; + + if ((elem->opt->start == -1) && (val > elem->opt->len)) /* >= 0x20 ??? */ + val = 0; + else + val = clamp(val - elem->opt->start, 0, elem->opt->len-1); + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + int changed = 0; + int err, oval, val; + + err = get_ctl_value(elem, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + elem->opt->start; + if (oval != val) { + err = set_ctl_value(elem, 0, val); + if (err < 0) + return err; + + changed = 1; + } + + return changed; +} + +static int scarlett_ctl_save_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = 0; + return 0; +} + +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + int err; + + if (ucontrol->value.enumerated.item[0] > 0) { + char buf[1] = { 0xa5 }; + + err = set_ctl_urb2(elem->mixer->chip, UAC2_CS_MEM, 0x005a, 0x3c, buf, 1); + if (err < 0) + return err; + + dev_info(&(elem->mixer->chip->dev->dev), + "Scarlett: Saved settings to hardware.\n"); + } + + return 0; /* (?) */ +} + +static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct scarlett_mixer_elem_info *elem = kctl->private_data; + unsigned char buf[2 * MAX_CHANNELS] = {0, }; + int err, val, i; + + err = get_ctl_urb2(elem->mixer->chip, UAC2_CS_MEM, elem->wValue, + elem->index, buf, elem->val_len * elem->count); + if (err < 0) { + dev_err(&(elem->mixer->chip->dev->dev), + "cannot get current value for mem %x: err = %d\n", + elem->wValue, err); + return err; + } + + if (elem->val_len == 1) { /* single U8 */ + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + } else { /* multiple S16 */ + for (i = 0; i < elem->count; i++) { + val = buf[2*i] | ((unsigned int)buf[2*i + 1] << 8); + if (val >= 0x8000) + val -= 0x10000; + } + } + + return 0; +} + +static struct snd_kcontrol_new usb_scarlett_ctl_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_switch_info, + .get = scarlett_ctl_switch_get, + .put = scarlett_ctl_switch_put, +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); + +static struct snd_kcontrol_new usb_scarlett_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_master = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_sync = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_meter_get, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_save = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_save_get, + .put = scarlett_ctl_save_put, +}; + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int offset, int num, + int val_len, int count, const char *name, + const struct scarlett_enum_info *opt, + struct scarlett_mixer_elem_info **elem_ret) +{ + struct snd_kcontrol *kctl; + struct scarlett_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->mixer = mixer; + elem->wValue = (offset << 8) | num; + elem->index = index; + elem->val_len = val_len; + elem->count = count; + elem->opt = opt; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + dev_err(&(elem->mixer->chip->dev->dev), "cannot malloc kcontrol\n"); + kfree(elem); + return -ENOMEM; + } + kctl->private_free = scarlett_mixer_elem_free; + + snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name); + + err = snd_ctl_add(mixer->chip->card, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +static int init_ctl(struct scarlett_mixer_elem_info *elem, int value) +{ + int err, channel; + + for (channel = 0; channel < elem->count; channel++) { + err = set_ctl_value(elem, channel, value); + if (err < 0) + return err; + } + + return 0; +} + +#define INIT(value) \ + do { \ + err = init_ctl(elem, value); \ + if (err < 0) \ + return err; \ + } while (0) + +#define CTL_SWITCH(cmd, off, no, count, name) \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, cmd, off, no, 2, count, name, NULL, &elem); \ + if (err < 0) \ + return err; \ + } while (0) + +/* no multichannel enum, always count == 1 (at least for now) */ +#define CTL_ENUM(cmd, off, no, name, opt) \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, cmd, off, no, 2, 1, name, opt, &elem); \ + if (err < 0) \ + return err; \ + } while (0) + +#define CTL_MIXER(cmd, off, no, count, name) \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl, cmd, off, no, 2, count, name, NULL, &elem); \ + if (err < 0) \ + return err; \ + INIT(-32768); /* -128*256 */ \ + } while (0) + +#define CTL_MASTER(cmd, off, no, count, name) \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, cmd, off, no, 2, count, name, NULL, &elem); \ + if (err < 0) \ + return err; \ + INIT(0); \ + } while (0) + +#define CTL_PEAK(cmd, off, no, count, name) /* but UAC2_CS_MEM */ \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl_meter, cmd, off, no, 2, count, name, NULL, NULL); \ + if (err < 0) \ + return err; \ + } while (0) + +static int add_output_ctls(struct usb_mixer_interface *mixer, + int index, const char *name, + const struct scarlett_device_info *info) +{ + int err; + char mx[45]; + struct scarlett_mixer_elem_info *elem; + + snprintf(mx, 45, "Master %d (%s) Playback Switch", index+1, name); /* mute */ + CTL_SWITCH(0x0a, 0x01, 2*index+1, 2, mx); + + snprintf(mx, 45, "Master %d (%s) Playback Volume", index+1, name); + CTL_MASTER(0x0a, 0x02, 2*index+1, 2, mx); + + snprintf(mx, 45, "Master %dL (%s) Source Playback Enum", index+1, name); + CTL_ENUM(0x33, 0x00, 2*index, mx, &info->opt_master); + INIT(info->mix_start); + + snprintf(mx, 45, "Master %dR (%s) Source Playback Enum", index+1, name); + CTL_ENUM(0x33, 0x00, 2*index+1, mx, &info->opt_master); + INIT(info->mix_start + 1); + + return 0; +} + +#define CTLS_OUTPUT(index, name) \ + do { \ + err = add_output_ctls(mixer, index, name, info); \ + if (err < 0) \ + return err;\ + } while (0) + +/********************** device-specific config *************************/ +static int scarlet_s6i6_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + struct scarlett_mixer_elem_info *elem; + int err; + + CTLS_OUTPUT(0, "Monitor"); + CTLS_OUTPUT(1, "Headphone 2"); + CTLS_OUTPUT(2, "SPDIF"); + + CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad); + + CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad); + + CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad); + CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad); + + return 0; +} + +static int scarlet_s8i6_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + struct scarlett_mixer_elem_info *elem; + int err; + + CTLS_OUTPUT(0, "Monitor"); + CTLS_OUTPUT(1, "Headphone"); + CTLS_OUTPUT(2, "SPDIF"); + + CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + + CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad); + CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad); + + return 0; +} + +static int scarlet_s18i6_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + struct scarlett_mixer_elem_info *elem; + int err; + + CTLS_OUTPUT(0, "Monitor"); + CTLS_OUTPUT(1, "Headphone"); + CTLS_OUTPUT(2, "SPDIF"); + + CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + + return 0; +} + +static int scarlet_s18i8_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + struct scarlett_mixer_elem_info *elem; + int err; + + CTLS_OUTPUT(0, "Monitor"); + CTLS_OUTPUT(1, "Headphone 1"); + CTLS_OUTPUT(2, "Headphone 2"); + CTLS_OUTPUT(3, "SPDIF"); + + CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad); + + CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad); + + CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad); + CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad); + + return 0; +} + +static int scarlet_s18i20_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ +/* struct scarlett_mixer_elem_info *elem; */ + int err; + + CTLS_OUTPUT(0, "Monitor"); /* 1/2 */ + CTLS_OUTPUT(1, "Line 3/4"); + CTLS_OUTPUT(2, "Line 5/6"); + CTLS_OUTPUT(3, "Line 7/8"); /* = Headphone 1 */ + CTLS_OUTPUT(4, "Line 9/10"); /* = Headphone 2 */ + CTLS_OUTPUT(5, "SPDIF"); + CTLS_OUTPUT(6, "ADAT 1/2"); + CTLS_OUTPUT(7, "ADAT 3/4"); + CTLS_OUTPUT(8, "ADAT 5/6"); + CTLS_OUTPUT(9, "ADAT 7/8"); + +/* ? real hardware switches + CTL_ENUM (0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM (0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad); + + CTL_ENUM (0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + CTL_ENUM (0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad); + + CTL_ENUM (0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad); + CTL_ENUM (0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad); +*/ + + return 0; +} + +static const char *s6i6_texts[] = { + txtOff, /* 'off' == 0xff */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, txtPcm7, txtPcm8, + txtPcm9, txtPcm10, txtPcm11, txtPcm12, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtSpdif1, txtSpdif2, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6, txtMix7, txtMix8 +}; + +/* untested... */ +static const struct scarlett_device_info s6i6_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 6, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 27, + .texts = s6i6_texts + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .texts = s6i6_texts + }, + + .controls_fn = scarlet_s6i6_controls, + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +/* and 2 loop channels: Mix1, Mix2 */ +static const char *s8i6_texts[] = { + txtOff, /* 'off' == 0xff */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, txtPcm7, txtPcm8, + txtPcm9, txtPcm10, txtPcm11, txtPcm12, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtSpdif1, txtSpdif2, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6 +}; + +/* untested... */ +static const struct scarlett_device_info s8i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 8, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 25, + .texts = s8i6_texts + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .texts = s8i6_texts + }, + + .controls_fn = scarlet_s8i6_controls, + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +static const char *s18i6_texts[] = { + txtOff, /* 'off' == 0xff */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8, + txtSpdif1, txtSpdif2, + txtAdat1, txtAdat2, txtAdat3, txtAdat4, + txtAdat5, txtAdat6, txtAdat7, txtAdat8, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6 +}; + +static const struct scarlett_device_info s18i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 18, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 6, + .spdif_start = 14, + .adat_start = 16, + .mix_start = 24, + + .opt_master = { + .start = -1, + .len = 31, + .texts = s18i6_texts + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .texts = s18i6_texts + }, + + .controls_fn = scarlet_s18i6_controls, + .matrix_mux_init = { + 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */ + 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */ + 14, 15, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static const char *s18i8_texts[] = { + txtOff, /* 'off' == 0xff (original software: 0x22) */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, txtPcm7, txtPcm8, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8, + txtSpdif1, txtSpdif2, + txtAdat1, txtAdat2, txtAdat3, txtAdat4, + txtAdat5, txtAdat6, txtAdat7, txtAdat8, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6, txtMix7, txtMix8 +}; + +static const struct scarlett_device_info s18i8_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 8, + + .pcm_start = 0, + .analog_start = 8, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 26, + + .opt_master = { + .start = -1, + .len = 35, + .texts = s18i8_texts + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .texts = s18i8_texts + }, + + .controls_fn = scarlet_s18i8_controls, + .matrix_mux_init = { + 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */ + 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */ + 16, 17, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static const char *s18i20_texts[] = { + txtOff, /* 'off' == 0xff (original software: 0x22) */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, txtPcm7, txtPcm8, + txtPcm9, txtPcm10, txtPcm11, txtPcm12, + txtPcm13, txtPcm14, txtPcm15, txtPcm16, + txtPcm17, txtPcm18, txtPcm19, txtPcm20, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8, + txtSpdif1, txtSpdif2, + txtAdat1, txtAdat2, txtAdat3, txtAdat4, + txtAdat5, txtAdat6, txtAdat7, txtAdat8, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6, txtMix7, txtMix8 +}; + +static const struct scarlett_device_info s18i20_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 20, + + .pcm_start = 0, + .analog_start = 20, + .spdif_start = 28, + .adat_start = 30, + .mix_start = 38, + + .opt_master = { + .start = -1, + .len = 47, + .texts = s18i20_texts + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .texts = s18i20_texts + }, + + .controls_fn = scarlet_s18i20_controls, + .matrix_mux_init = { + 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */ + 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */ + 28, 29, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +/* + * Create and initialize a mixer for the Focusrite(R) Scarlett + */ +int scarlett_mixer_controls(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[32]; + const struct scarlett_device_info *info; + struct scarlett_mixer_elem_info *elem; + static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; + + CTL_SWITCH(0x0a, 0x01, 0, 1, "Master Playback Switch"); + CTL_MASTER(0x0a, 0x02, 0, 1, "Master Playback Volume"); + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8012): + info = &s6i6_info; + break; + case USB_ID(0x1235, 0x8002): + info = &s8i6_info; + break; + case USB_ID(0x1235, 0x8004): + info = &s18i6_info; + break; + case USB_ID(0x1235, 0x8014): + info = &s18i8_info; + break; + case USB_ID(0x1235, 0x800c): + info = &s18i20_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + err = (*info->controls_fn)(mixer, info); + if (err < 0) + return err; + + for (i = 0; i < info->matrix_in; i++) { + snprintf(mx, 32, "Matrix %02d Input Playback Route", i+1); + CTL_ENUM(0x32, 0x06, i, mx, &info->opt_matrix); + INIT(info->matrix_mux_init[i]); + + for (o = 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, o+'A'); + CTL_MIXER(0x3c, 0x00, (i << 3) + (o & 0x07), 1, mx); + if (((o == 0) && (info->matrix_mux_init[i] == info->pcm_start)) || + ((o == 1) && (info->matrix_mux_init[i] == info->pcm_start + 1))) { + INIT(0); /* init hack: enable PCM 1 / 2 on Mix A / B */ + } + } + } + + for (i = 0; i < info->input_len; i++) { + snprintf(mx, 32, "Input Source %02d Capture Route", i+1); + CTL_ENUM(0x34, 0x00, i, mx, &info->opt_master); + INIT(info->analog_start + i); + } + + /* val_len == 1 needed here */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0, 1, + 1, "Sample Clock Source", &opt_clock, NULL); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, 1, + 1, "Sample Clock Sync Status", &opt_sync, NULL); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_save, 0x3c, 0x00, 0x5a, 1, + 1, "Save To HW", &opt_save, NULL); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + err = set_ctl_urb2(mixer->chip, UAC2_CS_CUR, 0x0100, 0x29, sample_rate_buffer, 4); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/usb/scarlett/scarlettmixer.h b/sound/usb/scarlett/scarlettmixer.h new file mode 100644 index 0000000..ebde17e --- /dev/null +++ b/sound/usb/scarlett/scarlettmixer.h @@ -0,0 +1,6 @@ +#ifndef __USBSCARLETTMIXER_H +#define __USBSCARLETTMIXER_H + +int scarlett_mixer_controls(struct usb_mixer_interface *mixer); + +#endif /* __USBSCARLETTMIXER_H */
At Tue, 21 Oct 2014 14:46:27 -0500, Chris J Arges wrote:
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. Finally the code was placed in its own subdirectory.
Author: Tobias Hoffman th55@gmx.de Author: Robin Gareus robin@gareus.org Signed-off-by: David Henningsson david.henningsson@canonical.com Signed-off-by: Chris J Arges chris.j.arges@canonical.com
sound/usb/Makefile | 1 + sound/usb/mixer.c | 27 +- sound/usb/quirks-table.h | 51 -- sound/usb/scarlett/scarlettmixer.c | 1264 ++++++++++++++++++++++++++++++++++++ sound/usb/scarlett/scarlettmixer.h | 6 + 5 files changed, 1293 insertions(+), 56 deletions(-) create mode 100644 sound/usb/scarlett/scarlettmixer.c create mode 100644 sound/usb/scarlett/scarlettmixer.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..4267e47 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -12,6 +12,7 @@ snd-usb-audio-objs := card.o \ pcm.o \ proc.o \ quirks.o \
scarlett/scarlettmixer.o \
Any reason to create a subdirectory although it's no individual driver? Put rather in the plain directory.
Overall, there seem too many copies from mixer.c. Can't we rather extend the stuff in mixer.c, make some of them global and let access from scalettmixer.c?
Takashi
On 10/22/2014 01:49 AM, Takashi Iwai wrote:
At Tue, 21 Oct 2014 14:46:27 -0500, Chris J Arges wrote:
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. Finally the code was placed in its own subdirectory.
Author: Tobias Hoffman th55@gmx.de Author: Robin Gareus robin@gareus.org Signed-off-by: David Henningsson david.henningsson@canonical.com Signed-off-by: Chris J Arges chris.j.arges@canonical.com
sound/usb/Makefile | 1 + sound/usb/mixer.c | 27 +- sound/usb/quirks-table.h | 51 -- sound/usb/scarlett/scarlettmixer.c | 1264 ++++++++++++++++++++++++++++++++++++ sound/usb/scarlett/scarlettmixer.h | 6 + 5 files changed, 1293 insertions(+), 56 deletions(-) create mode 100644 sound/usb/scarlett/scarlettmixer.c create mode 100644 sound/usb/scarlett/scarlettmixer.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..4267e47 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -12,6 +12,7 @@ snd-usb-audio-objs := card.o \ pcm.o \ proc.o \ quirks.o \
scarlett/scarlettmixer.o \
Any reason to create a subdirectory although it's no individual driver? Put rather in the plain directory.
I thought it would be neater, but I'll put the file back in the main sound/usb directory. If the code is small enough I could add it to mixer_quirks.c.
Overall, there seem too many copies from mixer.c. Can't we rather extend the stuff in mixer.c, make some of them global and let access from scalettmixer.c?
Yes, I can work on this, I think it would help reduce the code redundancy.
Takashi
Thank you, --chris
This is v3 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
I have only tested this on my own device which is a Focusrite Scarlett 18i8, any additional testing would be appreciated.
Chris J Arges (4): Revert "ALSA: usb-audio: Add quirk for Focusrite Scarlett ALSA: usb-audio: Add usb_mixer_elem_enum_info ALSA: usb-audio: make set_*_mix_values functions public ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
sound/usb/Makefile | 1 + sound/usb/mixer.c | 34 +- sound/usb/mixer.h | 15 + sound/usb/mixer_quirks.c | 18 +- sound/usb/mixer_scarlett.c | 1110 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + sound/usb/quirks-table.h | 51 -- 7 files changed, 1159 insertions(+), 76 deletions(-) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
This reverts commit 1762a59d8e8b5e99f6f4a0f292b40f3cacb108ba.
This quirk is not needed because support for the Scarlett mixers will be added.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/quirks-table.h | 51 ------------------------------------------------ 1 file changed, 51 deletions(-)
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index c657752..51686c7 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2667,57 +2667,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), .type = QUIRK_MIDI_NOVATION } }, -{ - /* - * Focusrite Scarlett 18i6 - * - * Avoid mixer creation, which otherwise fails because some of - * the interface descriptor subtypes for interface 0 are - * unknown. That should be fixed or worked-around but this at - * least allows the device to be used successfully with a DAW - * and an external mixer. See comments below about other - * ignored interfaces. - */ - USB_DEVICE(0x1235, 0x8004), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Focusrite", - .product_name = "Scarlett 18i6", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Device Firmware Update) */ - .ifnum = 5, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } - } - } -},
/* Access Music devices */ {
Add structure to hold enumeration control information and in addition add it to the usb_mixer_elem_info structure. This allows this structure to be more easily reused.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.h | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 73b1f64..cf3967a4 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -36,10 +36,17 @@ enum { USB_MIXER_U16, };
+struct usb_mixer_elem_enum_info { + int start; + int len; + const char * const *names; +}; + struct usb_mixer_elem_info { struct usb_mixer_interface *mixer; struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */ struct snd_ctl_elem_id *elem_id; + const struct usb_mixer_elem_enum_info *opt; /* enum control info */ unsigned int id; unsigned int control; /* CS or ICN (high byte) */ unsigned int cmask; /* channel mask bitmap: 0 = master */
At Wed, 29 Oct 2014 15:56:01 -0500, Chris J Arges wrote:
Add structure to hold enumeration control information and in addition add it to the usb_mixer_elem_info structure. This allows this structure to be more easily reused.
Since this information is specific to Scarlett, we don't have to put in the common place. Instead, define this locally in mixer_scarlett.c, redefine the mixer info struct like
struct scarlett_mixer_elem_info { struct usb_mixer_elem_info head; const struct scarlett_enum_info *opt; };
and allocate this struct, and cast between usb_mixer_elem_info and scarlett_mixer_elem_info.
Another way would be to add a pointer to usb_mixer_elem_info, but just an opaque void pointer instead of a specific type.
struct usb_mixer_elem_info { .... void *private_data; };
I don't mind either way.
Takashi
Signed-off-by: Chris J Arges chris.j.arges@canonical.com
sound/usb/mixer.h | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 73b1f64..cf3967a4 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -36,10 +36,17 @@ enum { USB_MIXER_U16, };
+struct usb_mixer_elem_enum_info {
- int start;
- int len;
- const char * const *names;
+};
struct usb_mixer_elem_info { struct usb_mixer_interface *mixer; struct usb_mixer_elem_info *next_id_elem; /* list of controls with same id */ struct snd_ctl_elem_id *elem_id;
- const struct usb_mixer_elem_enum_info *opt; /* enum control info */ unsigned int id; unsigned int control; /* CS or ICN (high byte) */ unsigned int cmask; /* channel mask bitmap: 0 = master */
-- 2.1.0
Make the functions set_cur_mix_value and get_cur_mix_value accessible by files that include mixer.h. In addition make usb_mixer_elem_free accessible. This allows reuse of these functions by mixers that may require quirks.
The following summarizes the renamed functions: - set_cur_mix_value -> snd_usb_set_cur_mix_value - get_cur_mix_value -> snd_usb_get_cur_mix_value - usb_mixer_elem_free -> snd_usb_mixer_elem_free
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.c | 34 +++++++++++++++++----------------- sound/usb/mixer.h | 8 ++++++++ sound/usb/mixer_quirks.c | 9 +-------- 3 files changed, 26 insertions(+), 25 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 2e4a9db..6b169fb 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -412,7 +412,7 @@ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, value); }
-static int get_cur_mix_value(struct usb_mixer_elem_info *cval, +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int *value) { int err; @@ -497,7 +497,7 @@ static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); }
-static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, +int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int value) { int err; @@ -815,7 +815,7 @@ static struct usb_feature_control_info audio_feature_info[] = { };
/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) +void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl) { kfree(kctl->private_data); kctl->private_data = NULL; @@ -998,7 +998,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, else test -= cval->res; if (test < cval->min || test > cval->max || - set_cur_mix_value(cval, minchn, 0, test) || + snd_usb_set_cur_mix_value(cval, minchn, 0, test) || get_cur_mix_raw(cval, minchn, &check)) { cval->res = last_valid_res; break; @@ -1007,7 +1007,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; cval->res *= 2; } - set_cur_mix_value(cval, minchn, 0, saved); + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); }
cval->initialized = 1; @@ -1086,7 +1086,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &val); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1096,7 +1096,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, return 0; } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &val); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1118,26 +1118,26 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &oval); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[cnt]; val = get_abs_value(cval, val); if (oval != val) { - set_cur_mix_value(cval, c + 1, cnt, val); + snd_usb_set_cur_mix_value(cval, c + 1, cnt, val); changed = 1; } cnt++; } } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &oval); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[0]; val = get_abs_value(cval, val); if (val != oval) { - set_cur_mix_value(cval, 0, 0, val); + snd_usb_set_cur_mix_value(cval, 0, 0, val); changed = 1; } } @@ -1250,7 +1250,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
/* * If all channels in the mask are marked read-only, make the control - * read-only. set_cur_mix_value() will check the mask again and won't + * read-only. snd_usb_set_cur_mix_value() will check the mask again and won't * issue write commands to read-only channels. */ if (cval->channels == readonly_mask) @@ -1263,7 +1263,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; @@ -1547,7 +1547,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (!len) @@ -1847,7 +1847,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, kfree(cval); return -ENOMEM; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) { /* nothing */ ; @@ -2530,7 +2530,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) if (!(cval->cmask & (1 << c))) continue; if (cval->cached & (1 << c)) { - err = set_cur_mix_value(cval, c + 1, idx, + err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) return err; @@ -2540,7 +2540,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) } else { /* master */ if (cval->cached) { - err = set_cur_mix_value(cval, 0, 0, *cval->cache_val); + err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); if (err < 0) return err; } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index cf3967a4..af4772e 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -82,4 +82,12 @@ 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_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, + int index, int value); + +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, + int channel, int index, int *value); + +extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl); + #endif /* __USBMIXER_H */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index f119a41..c9665bf 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -52,13 +52,6 @@ struct std_mono_table { snd_kcontrol_tlv_rw_t *tlv_callback; };
-/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) -{ - kfree(kctl->private_data); - kctl->private_data = NULL; -} - /* This function allows for the creation of standard UAC controls. * See the quirks for M-Audio FTUs or Ebox-44. * If you don't want to set a TLV callback pass NULL. @@ -108,7 +101,7 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer,
/* Set name */ snprintf(kctl->id.name, sizeof(kctl->id.name), name); - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
/* set TLV */ if (tlv_callback) {
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. The custom scarlett_mixer_elem_info struct was replaced with the more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/Makefile | 1 + sound/usb/mixer_quirks.c | 9 + sound/usb/mixer_scarlett.c | 1110 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + 4 files changed, 1126 insertions(+) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..bcee406 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -9,6 +9,7 @@ snd-usb-audio-objs := card.o \ helper.o \ mixer.o \ mixer_quirks.o \ + mixer_scarlett.o \ pcm.o \ proc.o \ quirks.o \ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index c9665bf..d06d27a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -41,6 +41,7 @@ #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" +#include "mixer_scarlett.h" #include "helper.h"
extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; @@ -1664,6 +1665,14 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) /* detection is disabled in mixer_maps.c */ err = snd_create_std_mono_table(mixer, ebox44_table); break; + + case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */ + case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */ + case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */ + case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */ + case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */ + err = snd_scarlett_controls_create(mixer); + break; }
return err; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c new file mode 100644 index 0000000..816880f --- /dev/null +++ b/sound/usb/mixer_scarlett.c @@ -0,0 +1,1110 @@ +/* + * Scarlett 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Rewritten and extended to support more models, e.g. Scarlett 18i8. + * + * Many features of Scarlett 18i6 do not lend themselves to be implemented + * as simple mixer-quirk -- or at least I don't see a way how to do that, yet. + * Hence the top parts of this file is a 1:1 copy of select static functions + * from mixer.c to implement the interface. + * Suggestions how to avoid this code duplication are very welcome. + * + * eventually this should either be integrated as quirk into mixer_quirks.c + * or become a standalone module. + * + * This source hardcodes the URBs for the Scarlett, + * 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. + * + * NB. Neither the OSX nor the win driver provided by Focusrite performs + * discovery, they seem to operate the same as this driver. + */ + +/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface. + * + * The protocol was reverse engineered by looking at communication between + * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 + * (firmware v305) using wireshark and usbmon in January 2013. + * Extended in July 2013. + * + * this mixer gives complete access to all features of the device: + * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z) + * - select clock source + * - dynamic input to mixer-matrix assignment + * - 18 x 6 mixer-matrix gain stages + * - bus routing & volume control + * - save setting to hardware + * - automatic re-initialization on connect if device was power-cycled + * - peak monitoring of all 3 buses (18 input, 6 DAW input, 6 route chanels) + * (changing the samplerate and buffersize is supported by the PCM interface) + * + * + * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR) + * wIndex + * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + + * channel, data=Line/Inst (2bytes) + * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes) + * ?? wValue=0x0803/04, ?? (2bytes) + * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes) + * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes) + * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte) + * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes) + * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes) + * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes) + * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes) + * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes) + * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff) + * + * USB reads: (i.e. actually issued by original software) + * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!) + * 0x29 wValue=0x0100 sample-rate(4bytes) + * wValue=0x0200 ?? 1byte (only once) + * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ?? + * + * USB reads with bRequest = 0x03 = UAC2_CS_MEM + * 0x3c wValue=0x0002 1byte: sync status (locked=1) + * wValue=0x0000 18*2byte: peak meter (inputs) + * wValue=0x0001 8(?)*2byte: peak meter (mix) + * wValue=0x0003 6*2byte: peak meter (pcm/daw) + * + * USB write with bRequest = 0x03 + * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5 + * + * + * <ditaa> + * /--------------\ 18chn 6chn /--------------\ + * | Hardware in +--+-------\ /------+--+ ALSA PCM out | + * --------------/ | | | | --------------/ + * | | | | + * | v v | + * | +---------------+ | + * | \ Matrix Mux / | + * | +-----+-----+ | + * | | | + * | | 18chn | + * | v | + * | +-----------+ | + * | | Mixer | | + * | | Matrix | | + * | | | | + * | | 18x6 Gain | | + * | | stages | | + * | +-----+-----+ | + * | | | + * | | | + * | 18chn | 6chn | 6chn + * v v v + * ========================= + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +-----+-----+ +-----+-----+ + * | | + * | 6chn | + * v | + * +-------------+ | + * | Master Gain | | + * +------+------+ | + * | | + * | 6chn | 18chn + * | (3 stereo pairs) | + * /--------------\ | | /--------------\ + * | Hardware out |<--/ -->| ALSA PCM in | + * --------------/ --------------/ + * </ditaa> + * + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "power.h" + +#include "mixer_scarlett.h" + +/* some gui mixers can't handle negative ctl values */ +#define SND_SCARLETT_LEVEL_BIAS 128 + +struct scarlett_device_info { + int matrix_in; + int matrix_out; + int input_len; + int output_len; + + int pcm_start; + int analog_start; + int spdif_start; + int adat_start; + int mix_start; + + struct usb_mixer_elem_enum_info opt_master; + struct usb_mixer_elem_enum_info opt_matrix; + + int (*controls_fn)(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info); + + int matrix_mux_init[]; +}; + +/********************** Enum Strings *************************/ +static const char txtOff[] = "Off", + txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2", + txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4", + txtPcm5[] = "PCM 5", txtPcm6[] = "PCM 6", + txtPcm7[] = "PCM 7", txtPcm8[] = "PCM 8", + txtPcm9[] = "PCM 9", txtPcm10[] = "PCM 10", + txtPcm11[] = "PCM 11", txtPcm12[] = "PCM 12", + txtPcm13[] = "PCM 13", txtPcm14[] = "PCM 14", + txtPcm15[] = "PCM 15", txtPcm16[] = "PCM 16", + txtPcm17[] = "PCM 17", txtPcm18[] = "PCM 18", + txtPcm19[] = "PCM 19", txtPcm20[] = "PCM 20", + txtAnlg1[] = "Analog 1", txtAnlg2[] = "Analog 2", + txtAnlg3[] = "Analog 3", txtAnlg4[] = "Analog 4", + txtAnlg5[] = "Analog 5", txtAnlg6[] = "Analog 6", + txtAnlg7[] = "Analog 7", txtAnlg8[] = "Analog 8", + txtSpdif1[] = "SPDIF 1", txtSpdif2[] = "SPDIF 2", + txtAdat1[] = "ADAT 1", txtAdat2[] = "ADAT 2", + txtAdat3[] = "ADAT 3", txtAdat4[] = "ADAT 4", + txtAdat5[] = "ADAT 5", txtAdat6[] = "ADAT 6", + txtAdat7[] = "ADAT 7", txtAdat8[] = "ADAT 8", + txtMix1[] = "Mix A", txtMix2[] = "Mix B", + txtMix3[] = "Mix C", txtMix4[] = "Mix D", + txtMix5[] = "Mix E", txtMix6[] = "Mix F", + txtMix7[] = "Mix G", txtMix8[] = "Mix H"; + +static const struct usb_mixer_elem_enum_info opt_pad = { + .start = 0, + .len = 2, + .names = (const char *[]){ + txtOff, "-10dB" + } +}; + +static const struct usb_mixer_elem_enum_info opt_impedance = { + .start = 0, + .len = 2, + .names = (const char *[]){ + "Line", "Hi-Z" + } +}; + +static const struct usb_mixer_elem_enum_info opt_clock = { + .start = 1, + .len = 3, + .names = (const char *[]){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct usb_mixer_elem_enum_info opt_sync = { + .start = 0, + .len = 2, + .names = (const char *[]){ + "No Lock", "Locked" + } +}; + +static const struct usb_mixer_elem_enum_info opt_save = { + .start = 0, + .len = 2, + .names = (const char *[]){ + "---", "Save" + } +}; + +static int scarlett_ctl_switch_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_BOOLEAN; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = !val; /* alsa uses 0: on, 1: off */ + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_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 = -128 + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.max = (int)kctl->private_value + + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + + SND_SCARLETT_LEVEL_BIAS; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - + SND_SCARLETT_LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_ctl_enum_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_ENUMERATED; + uinfo->count = elem->channels; + uinfo->value.enumerated.items = elem->opt->len; + if (uinfo->value.enumerated.item > uinfo->value.enumerated.items - 1) + uinfo->value.enumerated.item = + uinfo->value.enumerated.items - 1; + strcpy(uinfo->value.enumerated.name, + elem->opt->names[uinfo->value.enumerated.item]); + return 0; +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int err, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &val); + if (err < 0) + return err; + + if ((elem->opt->start == -1) && (val > elem->opt->len)) /* >= 0x20 */ + val = 0; + else + val = clamp(val - elem->opt->start, 0, elem->opt->len-1); + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int changed = 0; + int err, oval, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + elem->opt->start; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, 0, 0, val); + if (err < 0) + return err; + + changed = 1; + } + + return changed; +} + +static int scarlett_ctl_save_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = 0; + return 0; +} + +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->mixer->chip; + char buf[] = { 0x00, 0xa5 }; + int err; + + if (ucontrol->value.enumerated.item[0] > 0) { + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) | + (0x3c << 8), buf, 2); + if (err < 0) + return err; + + dev_info(&(elem->mixer->chip->dev->dev), + "scarlett: saved settings to hardware.\n"); + } + return 0; +} + +static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->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->id << 8); + int err; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, elem->channels); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + return 0; +} + +static struct snd_kcontrol_new usb_scarlett_ctl_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_switch_info, + .get = scarlett_ctl_switch_get, + .put = scarlett_ctl_switch_put, +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); + +static struct snd_kcontrol_new usb_scarlett_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_master = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_sync = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_meter_get, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_save = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_save_get, + .put = scarlett_ctl_save_put, +}; + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int offset, int num, + int val_type, int channels, const char *name, + const struct usb_mixer_elem_enum_info *opt, + struct usb_mixer_elem_info **elem_ret +) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->mixer = mixer; + elem->control = offset; + elem->idx_off = num; + elem->id = index; + elem->val_type = val_type; + + elem->channels = channels; + elem->opt = opt; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + dev_err(&(mixer->chip->dev->dev), "cannot malloc kcontrol\n"); + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name); + + err = snd_ctl_add(mixer->chip->card, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +static int init_ctl(struct usb_mixer_elem_info *elem, int value) +{ + int err, channel; + + for (channel = 0; channel < elem->channels; channel++) { + err = snd_usb_set_cur_mix_value(elem, channel, channel, value); + if (err < 0) + return err; + } + return 0; +} + +#define INIT(value) \ + do { \ + err = init_ctl(elem, value); \ + if (err < 0) \ + return err; \ + } while (0) + +#define CTL_SWITCH(cmd, off, no, count, name) \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, cmd, off, \ + no, USB_MIXER_S16, count, name, NULL, &elem); \ + if (err < 0) \ + return err; \ + } while (0) + +/* no multichannel enum, always count == 1 (at least for now) */ +#define CTL_ENUM(cmd, off, no, name, opt) \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, cmd, off, \ + no, USB_MIXER_S16, 1, name, opt, &elem); \ + if (err < 0) \ + return err; \ + } while (0) + +#define CTL_MIXER(cmd, off, no, count, name) \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl, cmd, off, \ + no, USB_MIXER_S16, count, name, NULL, &elem); \ + if (err < 0) \ + return err; \ + INIT(-32768); /* -128*256 */ \ + } while (0) + +#define CTL_MASTER(cmd, off, no, count, name) \ + do { \ + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, cmd, off, \ + no, USB_MIXER_S16, count, name, NULL, &elem); \ + if (err < 0) \ + return err; \ + INIT(0); \ + } while (0) + +static int add_output_ctls(struct usb_mixer_interface *mixer, + int index, const char *name, + const struct scarlett_device_info *info) +{ + int err; + char mx[48]; + struct usb_mixer_elem_info *elem; + + /* Add mute switch */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch", + index + 1, name); + CTL_SWITCH(0x0a, 0x01, 2*index+1, 2, mx); + + /* Add volume control */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume", + index + 1, name); + CTL_MASTER(0x0a, 0x02, 2*index+1, 2, mx); + + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + CTL_ENUM(0x33, 0x00, 2*index, mx, &info->opt_master); + INIT(info->mix_start); + + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", + index + 1, name); + CTL_ENUM(0x33, 0x00, 2*index+1, mx, &info->opt_master); + INIT(info->mix_start + 1); + + return 0; +} + +#define CTLS_OUTPUT(index, name) \ + do { \ + err = add_output_ctls(mixer, index, name, info); \ + if (err < 0) \ + return err;\ + } while (0) + +/********************** device-specific config *************************/ +static int scarlet_s6i6_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + struct usb_mixer_elem_info *elem; + int err; + + CTLS_OUTPUT(0, "Monitor"); + CTLS_OUTPUT(1, "Headphone 2"); + CTLS_OUTPUT(2, "SPDIF"); + + CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad); + + CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad); + + CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad); + CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad); + + return 0; +} + +static int scarlet_s8i6_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + struct usb_mixer_elem_info *elem; + int err; + + CTLS_OUTPUT(0, "Monitor"); + CTLS_OUTPUT(1, "Headphone"); + CTLS_OUTPUT(2, "SPDIF"); + + CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + + CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad); + CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad); + + return 0; +} + +static int scarlet_s18i6_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + struct usb_mixer_elem_info *elem; + int err; + + CTLS_OUTPUT(0, "Monitor"); + CTLS_OUTPUT(1, "Headphone"); + CTLS_OUTPUT(2, "SPDIF"); + + CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + + return 0; +} + +static int scarlet_s18i8_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + struct usb_mixer_elem_info *elem; + int err; + + CTLS_OUTPUT(0, "Monitor"); + CTLS_OUTPUT(1, "Headphone 1"); + CTLS_OUTPUT(2, "Headphone 2"); + CTLS_OUTPUT(3, "SPDIF"); + + CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad); + + CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad); + + CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad); + CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad); + + return 0; +} + +static int scarlet_s18i20_controls(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + int err; + + CTLS_OUTPUT(0, "Monitor"); /* 1/2 */ + CTLS_OUTPUT(1, "Line 3/4"); + CTLS_OUTPUT(2, "Line 5/6"); + CTLS_OUTPUT(3, "Line 7/8"); /* = Headphone 1 */ + CTLS_OUTPUT(4, "Line 9/10"); /* = Headphone 2 */ + CTLS_OUTPUT(5, "SPDIF"); + CTLS_OUTPUT(6, "ADAT 1/2"); + CTLS_OUTPUT(7, "ADAT 3/4"); + CTLS_OUTPUT(8, "ADAT 5/6"); + CTLS_OUTPUT(9, "ADAT 7/8"); + +/* ? real hardware switches + CTL_ENUM (0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance); + CTL_ENUM (0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad); + + CTL_ENUM (0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance); + CTL_ENUM (0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad); + + CTL_ENUM (0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad); + CTL_ENUM (0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad); +*/ + + return 0; +} + +static const char * const s6i6_names[] = { + txtOff, /* 'off' == 0xff */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, txtPcm7, txtPcm8, + txtPcm9, txtPcm10, txtPcm11, txtPcm12, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtSpdif1, txtSpdif2, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6, txtMix7, txtMix8 +}; + +/* untested... */ +static const struct scarlett_device_info s6i6_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 6, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 27, + .names = s6i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = s6i6_names + }, + + .controls_fn = scarlet_s6i6_controls, + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +/* and 2 loop channels: Mix1, Mix2 */ +static const char * const s8i6_names[] = { + txtOff, /* 'off' == 0xff */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, txtPcm7, txtPcm8, + txtPcm9, txtPcm10, txtPcm11, txtPcm12, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtSpdif1, txtSpdif2, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6 +}; + +/* untested... */ +static const struct scarlett_device_info s8i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 8, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 25, + .names = s8i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = s8i6_names + }, + + .controls_fn = scarlet_s8i6_controls, + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +static const char * const s18i6_names[] = { + txtOff, /* 'off' == 0xff */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8, + txtSpdif1, txtSpdif2, + txtAdat1, txtAdat2, txtAdat3, txtAdat4, + txtAdat5, txtAdat6, txtAdat7, txtAdat8, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6 +}; + +static const struct scarlett_device_info s18i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 18, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 6, + .spdif_start = 14, + .adat_start = 16, + .mix_start = 24, + + .opt_master = { + .start = -1, + .len = 31, + .names = s18i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .names = s18i6_names + }, + + .controls_fn = scarlet_s18i6_controls, + .matrix_mux_init = { + 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */ + 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */ + 14, 15, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static const char * const s18i8_names[] = { + txtOff, /* 'off' == 0xff (original software: 0x22) */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, txtPcm7, txtPcm8, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8, + txtSpdif1, txtSpdif2, + txtAdat1, txtAdat2, txtAdat3, txtAdat4, + txtAdat5, txtAdat6, txtAdat7, txtAdat8, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6, txtMix7, txtMix8 +}; + +static const struct scarlett_device_info s18i8_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 8, + + .pcm_start = 0, + .analog_start = 8, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 26, + + .opt_master = { + .start = -1, + .len = 35, + .names = s18i8_names + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .names = s18i8_names + }, + + .controls_fn = scarlet_s18i8_controls, + .matrix_mux_init = { + 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */ + 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */ + 16, 17, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static const char * const s18i20_names[] = { + txtOff, /* 'off' == 0xff (original software: 0x22) */ + txtPcm1, txtPcm2, txtPcm3, txtPcm4, + txtPcm5, txtPcm6, txtPcm7, txtPcm8, + txtPcm9, txtPcm10, txtPcm11, txtPcm12, + txtPcm13, txtPcm14, txtPcm15, txtPcm16, + txtPcm17, txtPcm18, txtPcm19, txtPcm20, + txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4, + txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8, + txtSpdif1, txtSpdif2, + txtAdat1, txtAdat2, txtAdat3, txtAdat4, + txtAdat5, txtAdat6, txtAdat7, txtAdat8, + txtMix1, txtMix2, txtMix3, txtMix4, + txtMix5, txtMix6, txtMix7, txtMix8 +}; + +static const struct scarlett_device_info s18i20_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 20, + + .pcm_start = 0, + .analog_start = 20, + .spdif_start = 28, + .adat_start = 30, + .mix_start = 38, + + .opt_master = { + .start = -1, + .len = 47, + .names = s18i20_names + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .names = s18i20_names + }, + + .controls_fn = scarlet_s18i20_controls, + .matrix_mux_init = { + 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */ + 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */ + 28, 29, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +/* + * Create and initialize a mixer for the Focusrite(R) Scarlett + */ +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[32]; + const struct scarlett_device_info *info; + struct usb_mixer_elem_info *elem; + static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; + + CTL_SWITCH(0x0a, 0x01, 0, 1, "Master Playback Switch"); + CTL_MASTER(0x0a, 0x02, 0, 1, "Master Playback Volume"); + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8012): + info = &s6i6_info; + break; + case USB_ID(0x1235, 0x8002): + info = &s8i6_info; + break; + case USB_ID(0x1235, 0x8004): + info = &s18i6_info; + break; + case USB_ID(0x1235, 0x8014): + info = &s18i8_info; + break; + case USB_ID(0x1235, 0x800c): + info = &s18i20_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + err = (*info->controls_fn)(mixer, info); + if (err < 0) + return err; + + for (i = 0; i < info->matrix_in; i++) { + snprintf(mx, 32, "Matrix %02d Input Playback Route", i+1); + CTL_ENUM(0x32, 0x06, i, mx, &info->opt_matrix); + INIT(info->matrix_mux_init[i]); + + for (o = 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); + CTL_MIXER(0x3c, 0x00, (i << 3) + (o & 0x07), 1, mx); + if (((o == 0) && + (info->matrix_mux_init[i] == info->pcm_start)) || + ((o == 1) && + (info->matrix_mux_init[i] == info->pcm_start + 1)) + ) { + INIT(0); /* hack: enable PCM 1/2 on Mix A/B */ + } + } + } + + for (i = 0; i < info->input_len; i++) { + snprintf(mx, 32, "Input Source %02d Capture Route", i+1); + CTL_ENUM(0x34, 0x00, i, mx, &info->opt_master); + INIT(info->analog_start + i); + } + + /* val_len == 1 needed here */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0, + USB_MIXER_U8, 1, "Sample Clock Source", + &opt_clock, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_save, 0x3c, 0x00, 0x5a, + USB_MIXER_U8, 1, "Save To HW", &opt_save, &elem); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + 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) | + (0x29 << 8), sample_rate_buffer, 4); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H + +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_H */
At Wed, 29 Oct 2014 15:56:03 -0500, Chris J Arges wrote:
+/********************** Enum Strings *************************/ +static const char txtOff[] = "Off",
txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2",
txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4",
txtPcm5[] = "PCM 5", txtPcm6[] = "PCM 6",
txtPcm7[] = "PCM 7", txtPcm8[] = "PCM 8",
txtPcm9[] = "PCM 9", txtPcm10[] = "PCM 10",
txtPcm11[] = "PCM 11", txtPcm12[] = "PCM 12",
txtPcm13[] = "PCM 13", txtPcm14[] = "PCM 14",
txtPcm15[] = "PCM 15", txtPcm16[] = "PCM 16",
txtPcm17[] = "PCM 17", txtPcm18[] = "PCM 18",
txtPcm19[] = "PCM 19", txtPcm20[] = "PCM 20",
txtAnlg1[] = "Analog 1", txtAnlg2[] = "Analog 2",
txtAnlg3[] = "Analog 3", txtAnlg4[] = "Analog 4",
txtAnlg5[] = "Analog 5", txtAnlg6[] = "Analog 6",
txtAnlg7[] = "Analog 7", txtAnlg8[] = "Analog 8",
txtSpdif1[] = "SPDIF 1", txtSpdif2[] = "SPDIF 2",
txtAdat1[] = "ADAT 1", txtAdat2[] = "ADAT 2",
txtAdat3[] = "ADAT 3", txtAdat4[] = "ADAT 4",
txtAdat5[] = "ADAT 5", txtAdat6[] = "ADAT 6",
txtAdat7[] = "ADAT 7", txtAdat8[] = "ADAT 8",
txtMix1[] = "Mix A", txtMix2[] = "Mix B",
txtMix3[] = "Mix C", txtMix4[] = "Mix D",
txtMix5[] = "Mix E", txtMix6[] = "Mix F",
txtMix7[] = "Mix G", txtMix8[] = "Mix H";
This is too ugly. Can we generate strings systematically?
+static const struct usb_mixer_elem_enum_info opt_pad = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
Better to be "const char * const []"?
txtOff, "-10dB"
- }
+};
+static const struct usb_mixer_elem_enum_info opt_impedance = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
"Line", "Hi-Z"
- }
+};
+static const struct usb_mixer_elem_enum_info opt_clock = {
- .start = 1,
- .len = 3,
- .names = (const char *[]){
"Internal", "SPDIF", "ADAT"
- }
+};
+static const struct usb_mixer_elem_enum_info opt_sync = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
"No Lock", "Locked"
- }
+};
+static const struct usb_mixer_elem_enum_info opt_save = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
"---", "Save"
- }
+};
This enum item look strange.
+static int scarlett_ctl_switch_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_BOOLEAN;
- uinfo->count = elem->channels;
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = 1;
- return 0;
+}
Use snd_ctl_boolean_mono_info().
+static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int i, err, val;
- for (i = 0; i < elem->channels; i++) {
err = snd_usb_get_cur_mix_value(elem, i, i, &val);
if (err < 0)
return err;
val = !val; /* alsa uses 0: on, 1: off */
Hm? ALSA uses 0:off 1:on in general. The meaning of "mute" is inverted -- it turns off when it's 1. So, in mixer.c, the mute control is assigned as USB_MIXER_INV_BOOLEAN.
ucontrol->value.integer.value[i] = val;
- }
- return 0;
+}
+static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int i, changed = 0;
- int err, oval, val;
- for (i = 0; i < elem->channels; i++) {
err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
if (err < 0)
return err;
val = ucontrol->value.integer.value[i];
val = !val;
if (oval != val) {
err = snd_usb_set_cur_mix_value(elem, i, i, val);
if (err < 0)
return err;
changed = 1;
}
- }
- return changed;
+}
+static int scarlett_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 = -128 + SND_SCARLETT_LEVEL_BIAS;
This is 0, right? IOW, SND_SCARLETT_LEVEL_BIAS was defined so that this becomes zero.
- uinfo->value.integer.max = (int)kctl->private_value +
SND_SCARLETT_LEVEL_BIAS;
- uinfo->value.integer.step = 1;
- return 0;
+}
+static int scarlett_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int i, err, val;
- for (i = 0; i < elem->channels; i++) {
err = snd_usb_get_cur_mix_value(elem, i, i, &val);
if (err < 0)
return err;
val = clamp(val / 256, -128, (int)kctl->private_value) +
SND_SCARLETT_LEVEL_BIAS;
ucontrol->value.integer.value[i] = val;
- }
- return 0;
+}
+static int scarlett_ctl_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int i, changed = 0;
- int err, oval, val;
- for (i = 0; i < elem->channels; i++) {
err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
if (err < 0)
return err;
val = ucontrol->value.integer.value[i] -
SND_SCARLETT_LEVEL_BIAS;
val = val * 256;
if (oval != val) {
err = snd_usb_set_cur_mix_value(elem, i, i, val);
if (err < 0)
return err;
changed = 1;
}
- }
- return changed;
+}
+static int scarlett_ctl_enum_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_ENUMERATED;
- uinfo->count = elem->channels;
- uinfo->value.enumerated.items = elem->opt->len;
- if (uinfo->value.enumerated.item > uinfo->value.enumerated.items - 1)
uinfo->value.enumerated.item =
uinfo->value.enumerated.items - 1;
- strcpy(uinfo->value.enumerated.name,
elem->opt->names[uinfo->value.enumerated.item]);
- return 0;
+}
Use snd_ctl_enum_info().
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int err, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
- if (err < 0)
return err;
- if ((elem->opt->start == -1) && (val > elem->opt->len)) /* >= 0x20 */
val = 0;
- else
val = clamp(val - elem->opt->start, 0, elem->opt->len-1);
- ucontrol->value.enumerated.item[0] = val;
- return 0;
+}
+static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int changed = 0;
- int err, oval, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval);
- if (err < 0)
return err;
- val = ucontrol->value.integer.value[0];
- val = val + elem->opt->start;
- if (oval != val) {
err = snd_usb_set_cur_mix_value(elem, 0, 0, val);
if (err < 0)
return err;
changed = 1;
- }
- return changed;
+}
+static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- ucontrol->value.enumerated.item[0] = 0;
- return 0;
+}
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0] > 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c << 8), buf, 2);
if (err < 0)
return err;
dev_info(&(elem->mixer->chip->dev->dev),
"scarlett: saved settings to hardware.\n");
This can be usb_audio_info(chip, ...)
- }
- return 0;
+}
+static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->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->id << 8);
- int err;
- err = snd_usb_ctl_msg(chip->dev,
usb_rcvctrlpipe(chip->dev, 0),
UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_IN, wValue, idx, buf, elem->channels);
- if (err < 0)
return err;
- ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1);
- return 0;
+}
+static struct snd_kcontrol_new usb_scarlett_ctl_switch = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "",
- .info = scarlett_ctl_switch_info,
- .get = scarlett_ctl_switch_get,
- .put = scarlett_ctl_switch_put,
+};
+static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0);
+static struct snd_kcontrol_new usb_scarlett_ctl = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
- .name = "",
- .info = scarlett_ctl_info,
- .get = scarlett_ctl_get,
- .put = scarlett_ctl_put,
- .private_value = 6, /* max value */
- .tlv = { .p = db_scale_scarlett_gain }
+};
+static struct snd_kcontrol_new usb_scarlett_ctl_master = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
- .name = "",
- .info = scarlett_ctl_info,
- .get = scarlett_ctl_get,
- .put = scarlett_ctl_put,
- .private_value = 6, /* max value */
- .tlv = { .p = db_scale_scarlett_gain }
+};
+static struct snd_kcontrol_new usb_scarlett_ctl_enum = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "",
- .info = scarlett_ctl_enum_info,
- .get = scarlett_ctl_enum_get,
- .put = scarlett_ctl_enum_put,
+};
+static struct snd_kcontrol_new usb_scarlett_ctl_sync = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
- .name = "",
- .info = scarlett_ctl_enum_info,
- .get = scarlett_ctl_meter_get,
+};
+static struct snd_kcontrol_new usb_scarlett_ctl_save = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "",
- .info = scarlett_ctl_enum_info,
- .get = scarlett_ctl_save_get,
- .put = scarlett_ctl_save_put,
+};
+static int add_new_ctl(struct usb_mixer_interface *mixer,
const struct snd_kcontrol_new *ncontrol,
int index, int offset, int num,
int val_type, int channels, const char *name,
const struct usb_mixer_elem_enum_info *opt,
struct usb_mixer_elem_info **elem_ret
+) +{
- struct snd_kcontrol *kctl;
- struct usb_mixer_elem_info *elem;
- int err;
- elem = kzalloc(sizeof(*elem), GFP_KERNEL);
- if (!elem)
return -ENOMEM;
- elem->mixer = mixer;
- elem->control = offset;
- elem->idx_off = num;
- elem->id = index;
- elem->val_type = val_type;
- elem->channels = channels;
- elem->opt = opt;
- kctl = snd_ctl_new1(ncontrol, elem);
- if (!kctl) {
dev_err(&(mixer->chip->dev->dev), "cannot malloc kcontrol\n");
kfree(elem);
return -ENOMEM;
- }
- kctl->private_free = snd_usb_mixer_elem_free;
- snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name);
- err = snd_ctl_add(mixer->chip->card, kctl);
- if (err < 0)
return err;
- if (elem_ret)
*elem_ret = elem;
- return 0;
+}
+static int init_ctl(struct usb_mixer_elem_info *elem, int value) +{
- int err, channel;
- for (channel = 0; channel < elem->channels; channel++) {
err = snd_usb_set_cur_mix_value(elem, channel, channel, value);
if (err < 0)
return err;
- }
- return 0;
+}
+#define INIT(value) \
- do { \
err = init_ctl(elem, value); \
if (err < 0) \
return err; \
- } while (0)
+#define CTL_SWITCH(cmd, off, no, count, name) \
- do { \
err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, cmd, off, \
no, USB_MIXER_S16, count, name, NULL, &elem); \
if (err < 0) \
return err; \
- } while (0)
+/* no multichannel enum, always count == 1 (at least for now) */ +#define CTL_ENUM(cmd, off, no, name, opt) \
- do { \
err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, cmd, off, \
no, USB_MIXER_S16, 1, name, opt, &elem); \
if (err < 0) \
return err; \
- } while (0)
+#define CTL_MIXER(cmd, off, no, count, name) \
- do { \
err = add_new_ctl(mixer, &usb_scarlett_ctl, cmd, off, \
no, USB_MIXER_S16, count, name, NULL, &elem); \
if (err < 0) \
return err; \
INIT(-32768); /* -128*256 */ \
- } while (0)
+#define CTL_MASTER(cmd, off, no, count, name) \
- do { \
err = add_new_ctl(mixer, &usb_scarlett_ctl_master, cmd, off, \
no, USB_MIXER_S16, count, name, NULL, &elem); \
if (err < 0) \
return err; \
INIT(0); \
- } while (0)
+static int add_output_ctls(struct usb_mixer_interface *mixer,
int index, const char *name,
const struct scarlett_device_info *info)
+{
- int err;
- char mx[48];
- struct usb_mixer_elem_info *elem;
- /* Add mute switch */
- snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch",
index + 1, name);
- CTL_SWITCH(0x0a, 0x01, 2*index+1, 2, mx);
- /* Add volume control */
- snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume",
index + 1, name);
- CTL_MASTER(0x0a, 0x02, 2*index+1, 2, mx);
- /* Add L channel source playback enumeration */
- snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum",
index + 1, name);
- CTL_ENUM(0x33, 0x00, 2*index, mx, &info->opt_master);
- INIT(info->mix_start);
- /* Add R channel source playback enumeration */
- snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum",
index + 1, name);
- CTL_ENUM(0x33, 0x00, 2*index+1, mx, &info->opt_master);
- INIT(info->mix_start + 1);
- return 0;
+}
+#define CTLS_OUTPUT(index, name) \
- do { \
err = add_output_ctls(mixer, index, name, info); \
if (err < 0) \
return err;\
- } while (0)
Hmm, these macros... Can we build from a table instead of calling each function? This would reduce the code size significantly, too, and the code flow would be more obvious. The biggest disadvantage of this kind of macro is that the code flow is hidden. It doesn't show that it can return secretly at error.
Takashi
+/********************** device-specific config *************************/ +static int scarlet_s6i6_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- struct usb_mixer_elem_info *elem;
- int err;
- CTLS_OUTPUT(0, "Monitor");
- CTLS_OUTPUT(1, "Headphone 2");
- CTLS_OUTPUT(2, "SPDIF");
- CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
- return 0;
+}
+static int scarlet_s8i6_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- struct usb_mixer_elem_info *elem;
- int err;
- CTLS_OUTPUT(0, "Monitor");
- CTLS_OUTPUT(1, "Headphone");
- CTLS_OUTPUT(2, "SPDIF");
- CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
- return 0;
+}
+static int scarlet_s18i6_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- struct usb_mixer_elem_info *elem;
- int err;
- CTLS_OUTPUT(0, "Monitor");
- CTLS_OUTPUT(1, "Headphone");
- CTLS_OUTPUT(2, "SPDIF");
- CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- return 0;
+}
+static int scarlet_s18i8_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- struct usb_mixer_elem_info *elem;
- int err;
- CTLS_OUTPUT(0, "Monitor");
- CTLS_OUTPUT(1, "Headphone 1");
- CTLS_OUTPUT(2, "Headphone 2");
- CTLS_OUTPUT(3, "SPDIF");
- CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
- return 0;
+}
+static int scarlet_s18i20_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- int err;
- CTLS_OUTPUT(0, "Monitor"); /* 1/2 */
- CTLS_OUTPUT(1, "Line 3/4");
- CTLS_OUTPUT(2, "Line 5/6");
- CTLS_OUTPUT(3, "Line 7/8"); /* = Headphone 1 */
- CTLS_OUTPUT(4, "Line 9/10"); /* = Headphone 2 */
- CTLS_OUTPUT(5, "SPDIF");
- CTLS_OUTPUT(6, "ADAT 1/2");
- CTLS_OUTPUT(7, "ADAT 3/4");
- CTLS_OUTPUT(8, "ADAT 5/6");
- CTLS_OUTPUT(9, "ADAT 7/8");
+/* ? real hardware switches
- CTL_ENUM (0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM (0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
- CTL_ENUM (0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- CTL_ENUM (0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
- CTL_ENUM (0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
- CTL_ENUM (0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
+*/
- return 0;
+}
+static const char * const s6i6_names[] = {
- txtOff, /* 'off' == 0xff */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6, txtPcm7, txtPcm8,
- txtPcm9, txtPcm10, txtPcm11, txtPcm12,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtSpdif1, txtSpdif2,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6, txtMix7, txtMix8
+};
+/* untested... */ +static const struct scarlett_device_info s6i6_info = {
- .matrix_in = 18,
- .matrix_out = 8,
- .input_len = 6,
- .output_len = 6,
- .pcm_start = 0,
- .analog_start = 12,
- .spdif_start = 16,
- .adat_start = 18,
- .mix_start = 18,
- .opt_master = {
.start = -1,
.len = 27,
.names = s6i6_names
- },
- .opt_matrix = {
.start = -1,
.len = 19,
.names = s6i6_names
- },
- .controls_fn = scarlet_s6i6_controls,
- .matrix_mux_init = {
12, 13, 14, 15, /* Analog -> 1..4 */
16, 17, /* SPDIF -> 5,6 */
0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */
8, 9, 10, 11
- }
+};
+/* and 2 loop channels: Mix1, Mix2 */ +static const char * const s8i6_names[] = {
- txtOff, /* 'off' == 0xff */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6, txtPcm7, txtPcm8,
- txtPcm9, txtPcm10, txtPcm11, txtPcm12,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtSpdif1, txtSpdif2,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6
+};
+/* untested... */ +static const struct scarlett_device_info s8i6_info = {
- .matrix_in = 18,
- .matrix_out = 6,
- .input_len = 8,
- .output_len = 6,
- .pcm_start = 0,
- .analog_start = 12,
- .spdif_start = 16,
- .adat_start = 18,
- .mix_start = 18,
- .opt_master = {
.start = -1,
.len = 25,
.names = s8i6_names
- },
- .opt_matrix = {
.start = -1,
.len = 19,
.names = s8i6_names
- },
- .controls_fn = scarlet_s8i6_controls,
- .matrix_mux_init = {
12, 13, 14, 15, /* Analog -> 1..4 */
16, 17, /* SPDIF -> 5,6 */
0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */
8, 9, 10, 11
- }
+};
+static const char * const s18i6_names[] = {
- txtOff, /* 'off' == 0xff */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
- txtSpdif1, txtSpdif2,
- txtAdat1, txtAdat2, txtAdat3, txtAdat4,
- txtAdat5, txtAdat6, txtAdat7, txtAdat8,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6
+};
+static const struct scarlett_device_info s18i6_info = {
- .matrix_in = 18,
- .matrix_out = 6,
- .input_len = 18,
- .output_len = 6,
- .pcm_start = 0,
- .analog_start = 6,
- .spdif_start = 14,
- .adat_start = 16,
- .mix_start = 24,
- .opt_master = {
.start = -1,
.len = 31,
.names = s18i6_names
- },
- .opt_matrix = {
.start = -1,
.len = 25,
.names = s18i6_names
- },
- .controls_fn = scarlet_s18i6_controls,
- .matrix_mux_init = {
6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */
16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */
14, 15, /* SPDIF -> 15,16 */
0, 1 /* PCM[1,2] -> 17,18 */
- }
+};
+static const char * const s18i8_names[] = {
- txtOff, /* 'off' == 0xff (original software: 0x22) */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6, txtPcm7, txtPcm8,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
- txtSpdif1, txtSpdif2,
- txtAdat1, txtAdat2, txtAdat3, txtAdat4,
- txtAdat5, txtAdat6, txtAdat7, txtAdat8,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6, txtMix7, txtMix8
+};
+static const struct scarlett_device_info s18i8_info = {
- .matrix_in = 18,
- .matrix_out = 8,
- .input_len = 18,
- .output_len = 8,
- .pcm_start = 0,
- .analog_start = 8,
- .spdif_start = 16,
- .adat_start = 18,
- .mix_start = 26,
- .opt_master = {
.start = -1,
.len = 35,
.names = s18i8_names
- },
- .opt_matrix = {
.start = -1,
.len = 27,
.names = s18i8_names
- },
- .controls_fn = scarlet_s18i8_controls,
- .matrix_mux_init = {
8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */
18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */
16, 17, /* SPDIF -> 15,16 */
0, 1 /* PCM[1,2] -> 17,18 */
- }
+};
+static const char * const s18i20_names[] = {
- txtOff, /* 'off' == 0xff (original software: 0x22) */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6, txtPcm7, txtPcm8,
- txtPcm9, txtPcm10, txtPcm11, txtPcm12,
- txtPcm13, txtPcm14, txtPcm15, txtPcm16,
- txtPcm17, txtPcm18, txtPcm19, txtPcm20,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
- txtSpdif1, txtSpdif2,
- txtAdat1, txtAdat2, txtAdat3, txtAdat4,
- txtAdat5, txtAdat6, txtAdat7, txtAdat8,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6, txtMix7, txtMix8
+};
+static const struct scarlett_device_info s18i20_info = {
- .matrix_in = 18,
- .matrix_out = 8,
- .input_len = 18,
- .output_len = 20,
- .pcm_start = 0,
- .analog_start = 20,
- .spdif_start = 28,
- .adat_start = 30,
- .mix_start = 38,
- .opt_master = {
.start = -1,
.len = 47,
.names = s18i20_names
- },
- .opt_matrix = {
.start = -1,
.len = 39,
.names = s18i20_names
- },
- .controls_fn = scarlet_s18i20_controls,
- .matrix_mux_init = {
20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */
30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */
28, 29, /* SPDIF -> 15,16 */
0, 1 /* PCM[1,2] -> 17,18 */
- }
+};
+/*
- Create and initialize a mixer for the Focusrite(R) Scarlett
- */
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{
- int err, i, o;
- char mx[32];
- const struct scarlett_device_info *info;
- struct usb_mixer_elem_info *elem;
- static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
- CTL_SWITCH(0x0a, 0x01, 0, 1, "Master Playback Switch");
- CTL_MASTER(0x0a, 0x02, 0, 1, "Master Playback Volume");
- switch (mixer->chip->usb_id) {
- case USB_ID(0x1235, 0x8012):
info = &s6i6_info;
break;
- case USB_ID(0x1235, 0x8002):
info = &s8i6_info;
break;
- case USB_ID(0x1235, 0x8004):
info = &s18i6_info;
break;
- case USB_ID(0x1235, 0x8014):
info = &s18i8_info;
break;
- case USB_ID(0x1235, 0x800c):
info = &s18i20_info;
break;
- default: /* device not (yet) supported */
return -EINVAL;
- }
- err = (*info->controls_fn)(mixer, info);
- if (err < 0)
return err;
- for (i = 0; i < info->matrix_in; i++) {
snprintf(mx, 32, "Matrix %02d Input Playback Route", i+1);
CTL_ENUM(0x32, 0x06, i, mx, &info->opt_matrix);
INIT(info->matrix_mux_init[i]);
for (o = 0; o < info->matrix_out; o++) {
sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1,
o+'A');
CTL_MIXER(0x3c, 0x00, (i << 3) + (o & 0x07), 1, mx);
if (((o == 0) &&
(info->matrix_mux_init[i] == info->pcm_start)) ||
((o == 1) &&
(info->matrix_mux_init[i] == info->pcm_start + 1))
) {
INIT(0); /* hack: enable PCM 1/2 on Mix A/B */
}
}
- }
- for (i = 0; i < info->input_len; i++) {
snprintf(mx, 32, "Input Source %02d Capture Route", i+1);
CTL_ENUM(0x34, 0x00, i, mx, &info->opt_master);
INIT(info->analog_start + i);
- }
- /* val_len == 1 needed here */
- err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0,
USB_MIXER_U8, 1, "Sample Clock Source",
&opt_clock, &elem);
- if (err < 0)
return err;
- /* val_len == 1 and UAC2_CS_MEM */
- err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2,
USB_MIXER_U8, 1, "Sample Clock Sync Status",
&opt_sync, &elem);
- if (err < 0)
return err;
- /* val_len == 1 and UAC2_CS_MEM */
- err = add_new_ctl(mixer, &usb_scarlett_ctl_save, 0x3c, 0x00, 0x5a,
USB_MIXER_U8, 1, "Save To HW", &opt_save, &elem);
- if (err < 0)
return err;
- /* initialize sampling rate to 48000 */
- 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) |
(0x29 << 8), sample_rate_buffer, 4);
- if (err < 0)
return err;
- return 0;
+} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer);
+#endif /* __USB_MIXER_SCARLETT_H */
2.1.0
On 10/30/2014 02:43 AM, Takashi Iwai wrote:
At Wed, 29 Oct 2014 15:56:03 -0500, Chris J Arges wrote:
+/********************** Enum Strings *************************/ +static const char txtOff[] = "Off",
txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2",
txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4",
txtPcm5[] = "PCM 5", txtPcm6[] = "PCM 6",
txtPcm7[] = "PCM 7", txtPcm8[] = "PCM 8",
txtPcm9[] = "PCM 9", txtPcm10[] = "PCM 10",
txtPcm11[] = "PCM 11", txtPcm12[] = "PCM 12",
txtPcm13[] = "PCM 13", txtPcm14[] = "PCM 14",
txtPcm15[] = "PCM 15", txtPcm16[] = "PCM 16",
txtPcm17[] = "PCM 17", txtPcm18[] = "PCM 18",
txtPcm19[] = "PCM 19", txtPcm20[] = "PCM 20",
txtAnlg1[] = "Analog 1", txtAnlg2[] = "Analog 2",
txtAnlg3[] = "Analog 3", txtAnlg4[] = "Analog 4",
txtAnlg5[] = "Analog 5", txtAnlg6[] = "Analog 6",
txtAnlg7[] = "Analog 7", txtAnlg8[] = "Analog 8",
txtSpdif1[] = "SPDIF 1", txtSpdif2[] = "SPDIF 2",
txtAdat1[] = "ADAT 1", txtAdat2[] = "ADAT 2",
txtAdat3[] = "ADAT 3", txtAdat4[] = "ADAT 4",
txtAdat5[] = "ADAT 5", txtAdat6[] = "ADAT 6",
txtAdat7[] = "ADAT 7", txtAdat8[] = "ADAT 8",
txtMix1[] = "Mix A", txtMix2[] = "Mix B",
txtMix3[] = "Mix C", txtMix4[] = "Mix D",
txtMix5[] = "Mix E", txtMix6[] = "Mix F",
txtMix7[] = "Mix G", txtMix8[] = "Mix H";
This is too ugly. Can we generate strings systematically?
Hi, at some point we need an array of static strings to pass into snd_ctl_enum_info, so in v3 I've created a macro to define the strings and created per device array of strings. I can do this using a function as well and dynamically allocate memory if that is a better approach.
+static const struct usb_mixer_elem_enum_info opt_pad = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
Better to be "const char * const []"?
txtOff, "-10dB"
- }
+};
+static const struct usb_mixer_elem_enum_info opt_impedance = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
"Line", "Hi-Z"
- }
+};
+static const struct usb_mixer_elem_enum_info opt_clock = {
- .start = 1,
- .len = 3,
- .names = (const char *[]){
"Internal", "SPDIF", "ADAT"
- }
+};
+static const struct usb_mixer_elem_enum_info opt_sync = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
"No Lock", "Locked"
- }
+};
+static const struct usb_mixer_elem_enum_info opt_save = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
"---", "Save"
- }
+};
This enum item look strange.
This control is activated much like a push button, so normally its in the "---" state and if you active it then it triggers the "Save to HW" function. Is there a better way to express this control?
+static int scarlett_ctl_switch_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_BOOLEAN;
- uinfo->count = elem->channels;
- uinfo->value.integer.min = 0;
- uinfo->value.integer.max = 1;
- return 0;
+}
Use snd_ctl_boolean_mono_info().
I've tried this but unfortunately some of the switch controls are stereo and some are mono. I could create two separate controls for mono/stereo, or perhaps I make this function more generate and add as 'snd_ctl_boolean_info' and allow it to check 'channels'?
+static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int i, err, val;
- for (i = 0; i < elem->channels; i++) {
err = snd_usb_get_cur_mix_value(elem, i, i, &val);
if (err < 0)
return err;
val = !val; /* alsa uses 0: on, 1: off */
Hm? ALSA uses 0:off 1:on in general. The meaning of "mute" is inverted -- it turns off when it's 1. So, in mixer.c, the mute control is assigned as USB_MIXER_INV_BOOLEAN.
ucontrol->value.integer.value[i] = val;
- }
- return 0;
+}
+static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int i, changed = 0;
- int err, oval, val;
- for (i = 0; i < elem->channels; i++) {
err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
if (err < 0)
return err;
val = ucontrol->value.integer.value[i];
val = !val;
if (oval != val) {
err = snd_usb_set_cur_mix_value(elem, i, i, val);
if (err < 0)
return err;
changed = 1;
}
- }
- return changed;
+}
+static int scarlett_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 = -128 + SND_SCARLETT_LEVEL_BIAS;
This is 0, right? IOW, SND_SCARLETT_LEVEL_BIAS was defined so that this becomes zero.
- uinfo->value.integer.max = (int)kctl->private_value +
SND_SCARLETT_LEVEL_BIAS;
- uinfo->value.integer.step = 1;
- return 0;
+}
+static int scarlett_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int i, err, val;
- for (i = 0; i < elem->channels; i++) {
err = snd_usb_get_cur_mix_value(elem, i, i, &val);
if (err < 0)
return err;
val = clamp(val / 256, -128, (int)kctl->private_value) +
SND_SCARLETT_LEVEL_BIAS;
ucontrol->value.integer.value[i] = val;
- }
- return 0;
+}
+static int scarlett_ctl_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int i, changed = 0;
- int err, oval, val;
- for (i = 0; i < elem->channels; i++) {
err = snd_usb_get_cur_mix_value(elem, i, i, &oval);
if (err < 0)
return err;
val = ucontrol->value.integer.value[i] -
SND_SCARLETT_LEVEL_BIAS;
val = val * 256;
if (oval != val) {
err = snd_usb_set_cur_mix_value(elem, i, i, val);
if (err < 0)
return err;
changed = 1;
}
- }
- return changed;
+}
+static int scarlett_ctl_enum_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_ENUMERATED;
- uinfo->count = elem->channels;
- uinfo->value.enumerated.items = elem->opt->len;
- if (uinfo->value.enumerated.item > uinfo->value.enumerated.items - 1)
uinfo->value.enumerated.item =
uinfo->value.enumerated.items - 1;
- strcpy(uinfo->value.enumerated.name,
elem->opt->names[uinfo->value.enumerated.item]);
- return 0;
+}
Use snd_ctl_enum_info().
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int err, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
- if (err < 0)
return err;
- if ((elem->opt->start == -1) && (val > elem->opt->len)) /* >= 0x20 */
val = 0;
- else
val = clamp(val - elem->opt->start, 0, elem->opt->len-1);
- ucontrol->value.enumerated.item[0] = val;
- return 0;
+}
+static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- int changed = 0;
- int err, oval, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval);
- if (err < 0)
return err;
- val = ucontrol->value.integer.value[0];
- val = val + elem->opt->start;
- if (oval != val) {
err = snd_usb_set_cur_mix_value(elem, 0, 0, val);
if (err < 0)
return err;
changed = 1;
- }
- return changed;
+}
+static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- ucontrol->value.enumerated.item[0] = 0;
- return 0;
+}
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0] > 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c << 8), buf, 2);
if (err < 0)
return err;
dev_info(&(elem->mixer->chip->dev->dev),
"scarlett: saved settings to hardware.\n");
This can be usb_audio_info(chip, ...)
- }
- return 0;
+}
+static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->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->id << 8);
- int err;
- err = snd_usb_ctl_msg(chip->dev,
usb_rcvctrlpipe(chip->dev, 0),
UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_IN, wValue, idx, buf, elem->channels);
- if (err < 0)
return err;
- ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1);
- return 0;
+}
+static struct snd_kcontrol_new usb_scarlett_ctl_switch = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "",
- .info = scarlett_ctl_switch_info,
- .get = scarlett_ctl_switch_get,
- .put = scarlett_ctl_switch_put,
+};
+static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0);
+static struct snd_kcontrol_new usb_scarlett_ctl = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
- .name = "",
- .info = scarlett_ctl_info,
- .get = scarlett_ctl_get,
- .put = scarlett_ctl_put,
- .private_value = 6, /* max value */
- .tlv = { .p = db_scale_scarlett_gain }
+};
+static struct snd_kcontrol_new usb_scarlett_ctl_master = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
- .name = "",
- .info = scarlett_ctl_info,
- .get = scarlett_ctl_get,
- .put = scarlett_ctl_put,
- .private_value = 6, /* max value */
- .tlv = { .p = db_scale_scarlett_gain }
+};
+static struct snd_kcontrol_new usb_scarlett_ctl_enum = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "",
- .info = scarlett_ctl_enum_info,
- .get = scarlett_ctl_enum_get,
- .put = scarlett_ctl_enum_put,
+};
+static struct snd_kcontrol_new usb_scarlett_ctl_sync = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
- .name = "",
- .info = scarlett_ctl_enum_info,
- .get = scarlett_ctl_meter_get,
+};
+static struct snd_kcontrol_new usb_scarlett_ctl_save = {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "",
- .info = scarlett_ctl_enum_info,
- .get = scarlett_ctl_save_get,
- .put = scarlett_ctl_save_put,
+};
+static int add_new_ctl(struct usb_mixer_interface *mixer,
const struct snd_kcontrol_new *ncontrol,
int index, int offset, int num,
int val_type, int channels, const char *name,
const struct usb_mixer_elem_enum_info *opt,
struct usb_mixer_elem_info **elem_ret
+) +{
- struct snd_kcontrol *kctl;
- struct usb_mixer_elem_info *elem;
- int err;
- elem = kzalloc(sizeof(*elem), GFP_KERNEL);
- if (!elem)
return -ENOMEM;
- elem->mixer = mixer;
- elem->control = offset;
- elem->idx_off = num;
- elem->id = index;
- elem->val_type = val_type;
- elem->channels = channels;
- elem->opt = opt;
- kctl = snd_ctl_new1(ncontrol, elem);
- if (!kctl) {
dev_err(&(mixer->chip->dev->dev), "cannot malloc kcontrol\n");
kfree(elem);
return -ENOMEM;
- }
- kctl->private_free = snd_usb_mixer_elem_free;
- snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name);
- err = snd_ctl_add(mixer->chip->card, kctl);
- if (err < 0)
return err;
- if (elem_ret)
*elem_ret = elem;
- return 0;
+}
+static int init_ctl(struct usb_mixer_elem_info *elem, int value) +{
- int err, channel;
- for (channel = 0; channel < elem->channels; channel++) {
err = snd_usb_set_cur_mix_value(elem, channel, channel, value);
if (err < 0)
return err;
- }
- return 0;
+}
+#define INIT(value) \
- do { \
err = init_ctl(elem, value); \
if (err < 0) \
return err; \
- } while (0)
+#define CTL_SWITCH(cmd, off, no, count, name) \
- do { \
err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, cmd, off, \
no, USB_MIXER_S16, count, name, NULL, &elem); \
if (err < 0) \
return err; \
- } while (0)
+/* no multichannel enum, always count == 1 (at least for now) */ +#define CTL_ENUM(cmd, off, no, name, opt) \
- do { \
err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, cmd, off, \
no, USB_MIXER_S16, 1, name, opt, &elem); \
if (err < 0) \
return err; \
- } while (0)
+#define CTL_MIXER(cmd, off, no, count, name) \
- do { \
err = add_new_ctl(mixer, &usb_scarlett_ctl, cmd, off, \
no, USB_MIXER_S16, count, name, NULL, &elem); \
if (err < 0) \
return err; \
INIT(-32768); /* -128*256 */ \
- } while (0)
+#define CTL_MASTER(cmd, off, no, count, name) \
- do { \
err = add_new_ctl(mixer, &usb_scarlett_ctl_master, cmd, off, \
no, USB_MIXER_S16, count, name, NULL, &elem); \
if (err < 0) \
return err; \
INIT(0); \
- } while (0)
+static int add_output_ctls(struct usb_mixer_interface *mixer,
int index, const char *name,
const struct scarlett_device_info *info)
+{
- int err;
- char mx[48];
- struct usb_mixer_elem_info *elem;
- /* Add mute switch */
- snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch",
index + 1, name);
- CTL_SWITCH(0x0a, 0x01, 2*index+1, 2, mx);
- /* Add volume control */
- snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume",
index + 1, name);
- CTL_MASTER(0x0a, 0x02, 2*index+1, 2, mx);
- /* Add L channel source playback enumeration */
- snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum",
index + 1, name);
- CTL_ENUM(0x33, 0x00, 2*index, mx, &info->opt_master);
- INIT(info->mix_start);
- /* Add R channel source playback enumeration */
- snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum",
index + 1, name);
- CTL_ENUM(0x33, 0x00, 2*index+1, mx, &info->opt_master);
- INIT(info->mix_start + 1);
- return 0;
+}
+#define CTLS_OUTPUT(index, name) \
- do { \
err = add_output_ctls(mixer, index, name, info); \
if (err < 0) \
return err;\
- } while (0)
Hmm, these macros... Can we build from a table instead of calling each function? This would reduce the code size significantly, too, and the code flow would be more obvious. The biggest disadvantage of this kind of macro is that the code flow is hidden. It doesn't show that it can return secretly at error.
I've got this mostly working, and will remove these macros in v3. The rest of the suggestions are implemented, and I've responded with some questions before I submit v3.
Thanks, --chris
Takashi
+/********************** device-specific config *************************/ +static int scarlet_s6i6_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- struct usb_mixer_elem_info *elem;
- int err;
- CTLS_OUTPUT(0, "Monitor");
- CTLS_OUTPUT(1, "Headphone 2");
- CTLS_OUTPUT(2, "SPDIF");
- CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
- return 0;
+}
+static int scarlet_s8i6_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- struct usb_mixer_elem_info *elem;
- int err;
- CTLS_OUTPUT(0, "Monitor");
- CTLS_OUTPUT(1, "Headphone");
- CTLS_OUTPUT(2, "SPDIF");
- CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
- return 0;
+}
+static int scarlet_s18i6_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- struct usb_mixer_elem_info *elem;
- int err;
- CTLS_OUTPUT(0, "Monitor");
- CTLS_OUTPUT(1, "Headphone");
- CTLS_OUTPUT(2, "SPDIF");
- CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- return 0;
+}
+static int scarlet_s18i8_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- struct usb_mixer_elem_info *elem;
- int err;
- CTLS_OUTPUT(0, "Monitor");
- CTLS_OUTPUT(1, "Headphone 1");
- CTLS_OUTPUT(2, "Headphone 2");
- CTLS_OUTPUT(3, "SPDIF");
- CTL_ENUM(0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- CTL_ENUM(0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
- CTL_ENUM(0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
- return 0;
+}
+static int scarlet_s18i20_controls(struct usb_mixer_interface *mixer,
const struct scarlett_device_info *info)
+{
- int err;
- CTLS_OUTPUT(0, "Monitor"); /* 1/2 */
- CTLS_OUTPUT(1, "Line 3/4");
- CTLS_OUTPUT(2, "Line 5/6");
- CTLS_OUTPUT(3, "Line 7/8"); /* = Headphone 1 */
- CTLS_OUTPUT(4, "Line 9/10"); /* = Headphone 2 */
- CTLS_OUTPUT(5, "SPDIF");
- CTLS_OUTPUT(6, "ADAT 1/2");
- CTLS_OUTPUT(7, "ADAT 3/4");
- CTLS_OUTPUT(8, "ADAT 5/6");
- CTLS_OUTPUT(9, "ADAT 7/8");
+/* ? real hardware switches
- CTL_ENUM (0x01, 0x09, 1, "Input 1 Impedance Switch", &opt_impedance);
- CTL_ENUM (0x01, 0x0b, 1, "Input 1 Pad Switch", &opt_pad);
- CTL_ENUM (0x01, 0x09, 2, "Input 2 Impedance Switch", &opt_impedance);
- CTL_ENUM (0x01, 0x0b, 2, "Input 2 Pad Switch", &opt_pad);
- CTL_ENUM (0x01, 0x0b, 3, "Input 3 Pad Switch", &opt_pad);
- CTL_ENUM (0x01, 0x0b, 4, "Input 4 Pad Switch", &opt_pad);
+*/
- return 0;
+}
+static const char * const s6i6_names[] = {
- txtOff, /* 'off' == 0xff */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6, txtPcm7, txtPcm8,
- txtPcm9, txtPcm10, txtPcm11, txtPcm12,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtSpdif1, txtSpdif2,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6, txtMix7, txtMix8
+};
+/* untested... */ +static const struct scarlett_device_info s6i6_info = {
- .matrix_in = 18,
- .matrix_out = 8,
- .input_len = 6,
- .output_len = 6,
- .pcm_start = 0,
- .analog_start = 12,
- .spdif_start = 16,
- .adat_start = 18,
- .mix_start = 18,
- .opt_master = {
.start = -1,
.len = 27,
.names = s6i6_names
- },
- .opt_matrix = {
.start = -1,
.len = 19,
.names = s6i6_names
- },
- .controls_fn = scarlet_s6i6_controls,
- .matrix_mux_init = {
12, 13, 14, 15, /* Analog -> 1..4 */
16, 17, /* SPDIF -> 5,6 */
0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */
8, 9, 10, 11
- }
+};
+/* and 2 loop channels: Mix1, Mix2 */ +static const char * const s8i6_names[] = {
- txtOff, /* 'off' == 0xff */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6, txtPcm7, txtPcm8,
- txtPcm9, txtPcm10, txtPcm11, txtPcm12,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtSpdif1, txtSpdif2,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6
+};
+/* untested... */ +static const struct scarlett_device_info s8i6_info = {
- .matrix_in = 18,
- .matrix_out = 6,
- .input_len = 8,
- .output_len = 6,
- .pcm_start = 0,
- .analog_start = 12,
- .spdif_start = 16,
- .adat_start = 18,
- .mix_start = 18,
- .opt_master = {
.start = -1,
.len = 25,
.names = s8i6_names
- },
- .opt_matrix = {
.start = -1,
.len = 19,
.names = s8i6_names
- },
- .controls_fn = scarlet_s8i6_controls,
- .matrix_mux_init = {
12, 13, 14, 15, /* Analog -> 1..4 */
16, 17, /* SPDIF -> 5,6 */
0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */
8, 9, 10, 11
- }
+};
+static const char * const s18i6_names[] = {
- txtOff, /* 'off' == 0xff */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
- txtSpdif1, txtSpdif2,
- txtAdat1, txtAdat2, txtAdat3, txtAdat4,
- txtAdat5, txtAdat6, txtAdat7, txtAdat8,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6
+};
+static const struct scarlett_device_info s18i6_info = {
- .matrix_in = 18,
- .matrix_out = 6,
- .input_len = 18,
- .output_len = 6,
- .pcm_start = 0,
- .analog_start = 6,
- .spdif_start = 14,
- .adat_start = 16,
- .mix_start = 24,
- .opt_master = {
.start = -1,
.len = 31,
.names = s18i6_names
- },
- .opt_matrix = {
.start = -1,
.len = 25,
.names = s18i6_names
- },
- .controls_fn = scarlet_s18i6_controls,
- .matrix_mux_init = {
6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */
16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */
14, 15, /* SPDIF -> 15,16 */
0, 1 /* PCM[1,2] -> 17,18 */
- }
+};
+static const char * const s18i8_names[] = {
- txtOff, /* 'off' == 0xff (original software: 0x22) */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6, txtPcm7, txtPcm8,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
- txtSpdif1, txtSpdif2,
- txtAdat1, txtAdat2, txtAdat3, txtAdat4,
- txtAdat5, txtAdat6, txtAdat7, txtAdat8,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6, txtMix7, txtMix8
+};
+static const struct scarlett_device_info s18i8_info = {
- .matrix_in = 18,
- .matrix_out = 8,
- .input_len = 18,
- .output_len = 8,
- .pcm_start = 0,
- .analog_start = 8,
- .spdif_start = 16,
- .adat_start = 18,
- .mix_start = 26,
- .opt_master = {
.start = -1,
.len = 35,
.names = s18i8_names
- },
- .opt_matrix = {
.start = -1,
.len = 27,
.names = s18i8_names
- },
- .controls_fn = scarlet_s18i8_controls,
- .matrix_mux_init = {
8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */
18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */
16, 17, /* SPDIF -> 15,16 */
0, 1 /* PCM[1,2] -> 17,18 */
- }
+};
+static const char * const s18i20_names[] = {
- txtOff, /* 'off' == 0xff (original software: 0x22) */
- txtPcm1, txtPcm2, txtPcm3, txtPcm4,
- txtPcm5, txtPcm6, txtPcm7, txtPcm8,
- txtPcm9, txtPcm10, txtPcm11, txtPcm12,
- txtPcm13, txtPcm14, txtPcm15, txtPcm16,
- txtPcm17, txtPcm18, txtPcm19, txtPcm20,
- txtAnlg1, txtAnlg2, txtAnlg3, txtAnlg4,
- txtAnlg5, txtAnlg6, txtAnlg7, txtAnlg8,
- txtSpdif1, txtSpdif2,
- txtAdat1, txtAdat2, txtAdat3, txtAdat4,
- txtAdat5, txtAdat6, txtAdat7, txtAdat8,
- txtMix1, txtMix2, txtMix3, txtMix4,
- txtMix5, txtMix6, txtMix7, txtMix8
+};
+static const struct scarlett_device_info s18i20_info = {
- .matrix_in = 18,
- .matrix_out = 8,
- .input_len = 18,
- .output_len = 20,
- .pcm_start = 0,
- .analog_start = 20,
- .spdif_start = 28,
- .adat_start = 30,
- .mix_start = 38,
- .opt_master = {
.start = -1,
.len = 47,
.names = s18i20_names
- },
- .opt_matrix = {
.start = -1,
.len = 39,
.names = s18i20_names
- },
- .controls_fn = scarlet_s18i20_controls,
- .matrix_mux_init = {
20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */
30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */
28, 29, /* SPDIF -> 15,16 */
0, 1 /* PCM[1,2] -> 17,18 */
- }
+};
+/*
- Create and initialize a mixer for the Focusrite(R) Scarlett
- */
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{
- int err, i, o;
- char mx[32];
- const struct scarlett_device_info *info;
- struct usb_mixer_elem_info *elem;
- static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' };
- CTL_SWITCH(0x0a, 0x01, 0, 1, "Master Playback Switch");
- CTL_MASTER(0x0a, 0x02, 0, 1, "Master Playback Volume");
- switch (mixer->chip->usb_id) {
- case USB_ID(0x1235, 0x8012):
info = &s6i6_info;
break;
- case USB_ID(0x1235, 0x8002):
info = &s8i6_info;
break;
- case USB_ID(0x1235, 0x8004):
info = &s18i6_info;
break;
- case USB_ID(0x1235, 0x8014):
info = &s18i8_info;
break;
- case USB_ID(0x1235, 0x800c):
info = &s18i20_info;
break;
- default: /* device not (yet) supported */
return -EINVAL;
- }
- err = (*info->controls_fn)(mixer, info);
- if (err < 0)
return err;
- for (i = 0; i < info->matrix_in; i++) {
snprintf(mx, 32, "Matrix %02d Input Playback Route", i+1);
CTL_ENUM(0x32, 0x06, i, mx, &info->opt_matrix);
INIT(info->matrix_mux_init[i]);
for (o = 0; o < info->matrix_out; o++) {
sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1,
o+'A');
CTL_MIXER(0x3c, 0x00, (i << 3) + (o & 0x07), 1, mx);
if (((o == 0) &&
(info->matrix_mux_init[i] == info->pcm_start)) ||
((o == 1) &&
(info->matrix_mux_init[i] == info->pcm_start + 1))
) {
INIT(0); /* hack: enable PCM 1/2 on Mix A/B */
}
}
- }
- for (i = 0; i < info->input_len; i++) {
snprintf(mx, 32, "Input Source %02d Capture Route", i+1);
CTL_ENUM(0x34, 0x00, i, mx, &info->opt_master);
INIT(info->analog_start + i);
- }
- /* val_len == 1 needed here */
- err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0,
USB_MIXER_U8, 1, "Sample Clock Source",
&opt_clock, &elem);
- if (err < 0)
return err;
- /* val_len == 1 and UAC2_CS_MEM */
- err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2,
USB_MIXER_U8, 1, "Sample Clock Sync Status",
&opt_sync, &elem);
- if (err < 0)
return err;
- /* val_len == 1 and UAC2_CS_MEM */
- err = add_new_ctl(mixer, &usb_scarlett_ctl_save, 0x3c, 0x00, 0x5a,
USB_MIXER_U8, 1, "Save To HW", &opt_save, &elem);
- if (err < 0)
return err;
- /* initialize sampling rate to 48000 */
- 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) |
(0x29 << 8), sample_rate_buffer, 4);
- if (err < 0)
return err;
- return 0;
+} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer);
+#endif /* __USB_MIXER_SCARLETT_H */
2.1.0
Chris J Arges wrote:
On 10/30/2014 02:43 AM, Takashi Iwai wrote:
Chris J Arges wrote:
+/********************** Enum Strings *************************/ +static const char txtOff[] = "Off",
txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2",
txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4",
txtPcm5[] = "PCM 5", txtPcm6[] = "PCM 6",
txtPcm7[] = "PCM 7", txtPcm8[] = "PCM 8",
txtPcm9[] = "PCM 9", txtPcm10[] = "PCM 10",
txtPcm11[] = "PCM 11", txtPcm12[] = "PCM 12",
txtPcm13[] = "PCM 13", txtPcm14[] = "PCM 14",
txtPcm15[] = "PCM 15", txtPcm16[] = "PCM 16",
txtPcm17[] = "PCM 17", txtPcm18[] = "PCM 18",
txtPcm19[] = "PCM 19", txtPcm20[] = "PCM 20",
txtAnlg1[] = "Analog 1", txtAnlg2[] = "Analog 2",
txtAnlg3[] = "Analog 3", txtAnlg4[] = "Analog 4",
txtAnlg5[] = "Analog 5", txtAnlg6[] = "Analog 6",
txtAnlg7[] = "Analog 7", txtAnlg8[] = "Analog 8",
txtSpdif1[] = "SPDIF 1", txtSpdif2[] = "SPDIF 2",
txtAdat1[] = "ADAT 1", txtAdat2[] = "ADAT 2",
txtAdat3[] = "ADAT 3", txtAdat4[] = "ADAT 4",
txtAdat5[] = "ADAT 5", txtAdat6[] = "ADAT 6",
txtAdat7[] = "ADAT 7", txtAdat8[] = "ADAT 8",
txtMix1[] = "Mix A", txtMix2[] = "Mix B",
txtMix3[] = "Mix C", txtMix4[] = "Mix D",
txtMix5[] = "Mix E", txtMix6[] = "Mix F",
txtMix7[] = "Mix G", txtMix8[] = "Mix H";
This is too ugly. Can we generate strings systematically?
Hi, at some point we need an array of static strings to pass into snd_ctl_enum_info
snd_ctl_enum_info() is a helper function to use in the common case when you have an array of static strings. If you create some strings dynamically, fill out info manually.
+static const struct usb_mixer_elem_enum_info opt_save = {
- .start = 0,
- .len = 2,
- .names = (const char *[]){
"---", "Save"
- }
+};
This enum item look strange.
This control is activated much like a push button, so normally its in the "---" state and if you active it then it triggers the "Save to HW" function.
"Save" is not a state.
Is there a better way to express this control?
A mixer control that allows no access but TLV_COMMAND. (Such a control will not be shown by 'normal' mixer applications.)
Regards, Clemens
At Wed, 29 Oct 2014 15:55:59 -0500, Chris J Arges wrote:
This is v3 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
Thanks, this is getting better. Will give review comments to each patch (if any).
Takashi
This is v4 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
[v3]
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
[v4]
This version removes the per-mixer control creation functions and uses a generic function based on structure data. Macros used for control addition are removed and the plain function is used instead. Hardcoded text block is removed and macros to define strings are used instead. Hardcoded control initialization has been removed.
Chris J Arges (4): Revert "ALSA: usb-audio: Add quirk for Focusrite Scarlett ALSA: usb-audio: Add private_data pointer to usb_mixer_elem_info ALSA: usb-audio: make set_*_mix_values functions public ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
sound/usb/Makefile | 1 + sound/usb/mixer.c | 34 +- sound/usb/mixer.h | 9 + sound/usb/mixer_quirks.c | 18 +- sound/usb/mixer_scarlett.c | 1070 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + sound/usb/quirks-table.h | 51 --- 7 files changed, 1113 insertions(+), 76 deletions(-) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
This reverts commit 1762a59d8e8b5e99f6f4a0f292b40f3cacb108ba.
This quirk is not needed because support for the Scarlett mixers will be added.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/quirks-table.h | 51 ------------------------------------------------ 1 file changed, 51 deletions(-)
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index c657752..51686c7 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2667,57 +2667,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), .type = QUIRK_MIDI_NOVATION } }, -{ - /* - * Focusrite Scarlett 18i6 - * - * Avoid mixer creation, which otherwise fails because some of - * the interface descriptor subtypes for interface 0 are - * unknown. That should be fixed or worked-around but this at - * least allows the device to be used successfully with a DAW - * and an external mixer. See comments below about other - * ignored interfaces. - */ - USB_DEVICE(0x1235, 0x8004), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Focusrite", - .product_name = "Scarlett 18i6", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Device Firmware Update) */ - .ifnum = 5, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } - } - } -},
/* Access Music devices */ {
Add a private_data pointer to usb_mixer_elem_info to allow other mixer implementations to extend the structure as necessary.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 73b1f64..8df9a73 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -53,6 +53,7 @@ struct usb_mixer_elem_info { int cached; int cache_val[MAX_CHANNELS]; u8 initialized; + void *private_data; };
int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
Make the functions set_cur_mix_value and get_cur_mix_value accessible by files that include mixer.h. In addition make usb_mixer_elem_free accessible. This allows reuse of these functions by mixers that may require quirks.
The following summarizes the renamed functions: - set_cur_mix_value -> snd_usb_set_cur_mix_value - get_cur_mix_value -> snd_usb_get_cur_mix_value - usb_mixer_elem_free -> snd_usb_mixer_elem_free
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.c | 34 +++++++++++++++++----------------- sound/usb/mixer.h | 8 ++++++++ sound/usb/mixer_quirks.c | 9 +-------- 3 files changed, 26 insertions(+), 25 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 2e4a9db..6b169fb 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -412,7 +412,7 @@ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, value); }
-static int get_cur_mix_value(struct usb_mixer_elem_info *cval, +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int *value) { int err; @@ -497,7 +497,7 @@ static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); }
-static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, +int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int value) { int err; @@ -815,7 +815,7 @@ static struct usb_feature_control_info audio_feature_info[] = { };
/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) +void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl) { kfree(kctl->private_data); kctl->private_data = NULL; @@ -998,7 +998,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, else test -= cval->res; if (test < cval->min || test > cval->max || - set_cur_mix_value(cval, minchn, 0, test) || + snd_usb_set_cur_mix_value(cval, minchn, 0, test) || get_cur_mix_raw(cval, minchn, &check)) { cval->res = last_valid_res; break; @@ -1007,7 +1007,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; cval->res *= 2; } - set_cur_mix_value(cval, minchn, 0, saved); + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); }
cval->initialized = 1; @@ -1086,7 +1086,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &val); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1096,7 +1096,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, return 0; } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &val); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1118,26 +1118,26 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &oval); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[cnt]; val = get_abs_value(cval, val); if (oval != val) { - set_cur_mix_value(cval, c + 1, cnt, val); + snd_usb_set_cur_mix_value(cval, c + 1, cnt, val); changed = 1; } cnt++; } } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &oval); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[0]; val = get_abs_value(cval, val); if (val != oval) { - set_cur_mix_value(cval, 0, 0, val); + snd_usb_set_cur_mix_value(cval, 0, 0, val); changed = 1; } } @@ -1250,7 +1250,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
/* * If all channels in the mask are marked read-only, make the control - * read-only. set_cur_mix_value() will check the mask again and won't + * read-only. snd_usb_set_cur_mix_value() will check the mask again and won't * issue write commands to read-only channels. */ if (cval->channels == readonly_mask) @@ -1263,7 +1263,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; @@ -1547,7 +1547,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (!len) @@ -1847,7 +1847,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, kfree(cval); return -ENOMEM; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) { /* nothing */ ; @@ -2530,7 +2530,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) if (!(cval->cmask & (1 << c))) continue; if (cval->cached & (1 << c)) { - err = set_cur_mix_value(cval, c + 1, idx, + err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) return err; @@ -2540,7 +2540,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) } else { /* master */ if (cval->cached) { - err = set_cur_mix_value(cval, 0, 0, *cval->cache_val); + err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); if (err < 0) return err; } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 8df9a73..8db2980 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -76,4 +76,12 @@ 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_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, + int index, int value); + +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, + int channel, int index, int *value); + +extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl); + #endif /* __USBMIXER_H */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index f119a41..c9665bf 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -52,13 +52,6 @@ struct std_mono_table { snd_kcontrol_tlv_rw_t *tlv_callback; };
-/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) -{ - kfree(kctl->private_data); - kctl->private_data = NULL; -} - /* This function allows for the creation of standard UAC controls. * See the quirks for M-Audio FTUs or Ebox-44. * If you don't want to set a TLV callback pass NULL. @@ -108,7 +101,7 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer,
/* Set name */ snprintf(kctl->id.name, sizeof(kctl->id.name), name); - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
/* set TLV */ if (tlv_callback) {
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. The custom scarlett_mixer_elem_info struct was replaced with the more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.
This patch also makes additional modifications based on upstream comments. Individual control creation functions are removed and a generic function is no used. Macros for function calls are removed to improve readability. Hardcoded control initialization is removed.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/Makefile | 1 + sound/usb/mixer_quirks.c | 9 + sound/usb/mixer_scarlett.c | 1070 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + 4 files changed, 1086 insertions(+) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..bcee406 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -9,6 +9,7 @@ snd-usb-audio-objs := card.o \ helper.o \ mixer.o \ mixer_quirks.o \ + mixer_scarlett.o \ pcm.o \ proc.o \ quirks.o \ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index c9665bf..d06d27a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -41,6 +41,7 @@ #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" +#include "mixer_scarlett.h" #include "helper.h"
extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; @@ -1664,6 +1665,14 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) /* detection is disabled in mixer_maps.c */ err = snd_create_std_mono_table(mixer, ebox44_table); break; + + case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */ + case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */ + case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */ + case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */ + case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */ + err = snd_scarlett_controls_create(mixer); + break; }
return err; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c new file mode 100644 index 0000000..d62f8d4 --- /dev/null +++ b/sound/usb/mixer_scarlett.c @@ -0,0 +1,1070 @@ +/* + * Scarlett 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Rewritten and extended to support more models, e.g. Scarlett 18i8. + * + * Many features of Scarlett 18i6 do not lend themselves to be implemented + * as simple mixer-quirk -- or at least I don't see a way how to do that, yet. + * Hence the top parts of this file is a 1:1 copy of select static functions + * from mixer.c to implement the interface. + * Suggestions how to avoid this code duplication are very welcome. + * + * eventually this should either be integrated as quirk into mixer_quirks.c + * or become a standalone module. + * + * This source hardcodes the URBs for the Scarlett, + * 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. + * + * NB. Neither the OSX nor the win driver provided by Focusrite performs + * discovery, they seem to operate the same as this driver. + */ + +/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface. + * + * The protocol was reverse engineered by looking at communication between + * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 + * (firmware v305) using wireshark and usbmon in January 2013. + * Extended in July 2013. + * + * this mixer gives complete access to all features of the device: + * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z) + * - select clock source + * - dynamic input to mixer-matrix assignment + * - 18 x 6 mixer-matrix gain stages + * - bus routing & volume control + * - save setting to hardware + * - automatic re-initialization on connect if device was power-cycled + * - peak monitoring of all 3 buses (18 input, 6 DAW input, 6 route channels) + * (changing the samplerate and buffersize is supported by the PCM interface) + * + * + * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR) + * wIndex + * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + + * channel, data=Line/Inst (2bytes) + * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes) + * ?? wValue=0x0803/04, ?? (2bytes) + * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes) + * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes) + * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte) + * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes) + * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes) + * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes) + * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes) + * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes) + * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff) + * + * USB reads: (i.e. actually issued by original software) + * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!) + * 0x29 wValue=0x0100 sample-rate(4bytes) + * wValue=0x0200 ?? 1byte (only once) + * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ?? + * + * USB reads with bRequest = 0x03 = UAC2_CS_MEM + * 0x3c wValue=0x0002 1byte: sync status (locked=1) + * wValue=0x0000 18*2byte: peak meter (inputs) + * wValue=0x0001 8(?)*2byte: peak meter (mix) + * wValue=0x0003 6*2byte: peak meter (pcm/daw) + * + * USB write with bRequest = 0x03 + * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5 + * + * + * <ditaa> + * /--------------\ 18chn 6chn /--------------\ + * | Hardware in +--+-------\ /------+--+ ALSA PCM out | + * --------------/ | | | | --------------/ + * | | | | + * | v v | + * | +---------------+ | + * | \ Matrix Mux / | + * | +-----+-----+ | + * | | | + * | | 18chn | + * | v | + * | +-----------+ | + * | | Mixer | | + * | | Matrix | | + * | | | | + * | | 18x6 Gain | | + * | | stages | | + * | +-----+-----+ | + * | | | + * | | | + * | 18chn | 6chn | 6chn + * v v v + * ========================= + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +-----+-----+ +-----+-----+ + * | | + * | 6chn | + * v | + * +-------------+ | + * | Master Gain | | + * +------+------+ | + * | | + * | 6chn | 18chn + * | (3 stereo pairs) | + * /--------------\ | | /--------------\ + * | Hardware out |<--/ -->| ALSA PCM in | + * --------------/ --------------/ + * </ditaa> + * + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "power.h" + +#include "mixer_scarlett.h" + +/* some gui mixers can't handle negative ctl values */ +#define SND_SCARLETT_LEVEL_BIAS 128 +#define SND_SCARLETT_MATRIX_IN_MAX 18 +#define SND_SCARLETT_CONTROLS_MAX 10 + +enum { + SCARLETT_OUTPUTS, + SCARLETT_SWITCH_IMPEDANCE, + SCARLETT_SWITCH_PAD, +}; + +struct scarlett_mixer_elem_enum_info { + int start; + int len; + const char * const *names; +}; + +struct scarlett_mixer_control { + unsigned char num; + unsigned char type; + const char *name; +}; + +struct scarlett_device_info { + int matrix_in; + int matrix_out; + int input_len; + int output_len; + + int pcm_start; + int analog_start; + int spdif_start; + int adat_start; + int mix_start; + + struct scarlett_mixer_elem_enum_info opt_master; + struct scarlett_mixer_elem_enum_info opt_matrix; + + int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX]; + + int num_controls; /* number of items in controls */ + const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX]; +}; + +/********************** Enum Strings *************************/ + +#define TXT_OFF "Off" +#define TXT_PCM(num) "PCM " #num +#define TXT_ANALOG(num) "Analog " #num +#define TXT_SPDIF(num) "SPDIF " #num +#define TXT_ADAT(num) "ADAT " #num +#define TXT_MIX(letter) "MIX " #letter + +static const struct scarlett_mixer_elem_enum_info opt_pad = { + .start = 0, + .len = 2, + .names = (const char * const[]){ + "0dB", "-10dB" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_impedance = { + .start = 0, + .len = 2, + .names = (const char * const[]){ + "Line", "Hi-Z" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_clock = { + .start = 1, + .len = 3, + .names = (const char * const[]){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_sync = { + .start = 0, + .len = 2, + .names = (const char * const[]){ + "No Lock", "Locked" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_save = { + .start = 0, + .len = 2, + .names = (const char * const[]){ + "---", "Save" + } +}; + +static int scarlett_ctl_switch_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_BOOLEAN; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = !val; /* invert mute logic for mixer */ + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_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 = (int)kctl->private_value + + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + + SND_SCARLETT_LEVEL_BIAS; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - + SND_SCARLETT_LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + + return snd_ctl_enum_info(uinfo, elem->channels, opt->len, opt->names); +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &val); + if (err < 0) + return err; + + if ((opt->start == -1) && (val > opt->len)) /* >= 0x20 */ + val = 0; + else + val = clamp(val - opt->start, 0, opt->len-1); + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int changed = 0; + int err, oval, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + opt->start; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, 0, 0, val); + if (err < 0) + return err; + + changed = 1; + } + + return changed; +} + +static int scarlett_ctl_save_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = 0; + return 0; +} + +static int scarlett_ctl_save_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->mixer->chip; + char buf[] = { 0x00, 0xa5 }; + int err; + + if (ucontrol->value.enumerated.item[0] > 0) { + err = snd_usb_ctl_msg(chip->dev, + usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) | + (0x3c << 8), buf, 2); + if (err < 0) + return err; + + usb_audio_info(elem->mixer->chip, + "scarlett: saved settings to hardware.\n"); + } + return 0; +} + +static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->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->id << 8); + int err; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, elem->channels); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + return 0; +} + +static struct snd_kcontrol_new usb_scarlett_ctl_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_switch_info, + .get = scarlett_ctl_switch_get, + .put = scarlett_ctl_switch_put, +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); + +static struct snd_kcontrol_new usb_scarlett_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_master = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_sync = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_meter_get, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_save = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_save_get, + .put = scarlett_ctl_save_put, +}; + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int offset, int num, + int val_type, int channels, const char *name, + const struct scarlett_mixer_elem_enum_info *opt, + struct usb_mixer_elem_info **elem_ret +) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->mixer = mixer; + elem->control = offset; + elem->idx_off = num; + elem->id = index; + elem->val_type = val_type; + + elem->channels = channels; + + /* add scarlett_mixer_elem_enum_info struct */ + elem->private_data = (void *)opt; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + usb_audio_err(mixer->chip, "cannot malloc kcontrol\n"); + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name); + + err = snd_ctl_add(mixer->chip->card, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +static int add_output_ctls(struct usb_mixer_interface *mixer, + int index, const char *name, + const struct scarlett_device_info *info) +{ + int err; + char mx[48]; + struct usb_mixer_elem_info *elem; + + /* Add mute switch */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add volume control and initialize to 0 */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index, + USB_MIXER_S16, 1, mx, &info->opt_master, &elem); + if (err < 0) + return err; + + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index+1, + USB_MIXER_S16, 1, mx, &info->opt_master, &elem); + if (err < 0) + return err; + + return 0; +} + +/********************** device-specific config *************************/ + +static const char * const s6i6_names[] = { + TXT_OFF, + TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4), + TXT_PCM(5), TXT_PCM(6), TXT_PCM(7), TXT_PCM(8), + TXT_PCM(9), TXT_PCM(10), TXT_PCM(11), TXT_PCM(12), + TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4), + TXT_SPDIF(1), TXT_SPDIF(2), + TXT_MIX(A), TXT_MIX(B), TXT_MIX(C), TXT_MIX(D), + TXT_MIX(E), TXT_MIX(F), TXT_MIX(G), TXT_MIX(H), +}; + +/* untested... */ +static const struct scarlett_device_info s6i6_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 6, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 27, + .names = s6i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = s6i6_names + }, + + .num_controls = 0, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +/* and 2 loop channels: Mix1, Mix2 */ +static const char * const s8i6_names[] = { + TXT_OFF, + TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4), + TXT_PCM(5), TXT_PCM(6), TXT_PCM(7), TXT_PCM(8), + TXT_PCM(9), TXT_PCM(10), TXT_PCM(11), TXT_PCM(12), + TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4), + TXT_SPDIF(1), TXT_SPDIF(2), + TXT_MIX(A), TXT_MIX(B), TXT_MIX(C), TXT_MIX(D), + TXT_MIX(E), TXT_MIX(F), +}; + +/* untested... */ +static const struct scarlett_device_info s8i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 8, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 25, + .names = s8i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = s8i6_names + }, + + .num_controls = 7, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +static const char * const s18i6_names[] = { + TXT_OFF, + TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4), + TXT_PCM(5), TXT_PCM(6), + TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4), + TXT_ANALOG(5), TXT_ANALOG(6), TXT_ANALOG(7), TXT_ANALOG(8), + TXT_SPDIF(1), TXT_SPDIF(2), + TXT_ADAT(1), TXT_ADAT(2), TXT_ADAT(3), TXT_ADAT(4), + TXT_ADAT(5), TXT_ADAT(6), TXT_ADAT(7), TXT_ADAT(8), + TXT_MIX(A), TXT_MIX(B), TXT_MIX(C), TXT_MIX(D), + TXT_MIX(E), TXT_MIX(F), +}; + +static const struct scarlett_device_info s18i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 18, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 6, + .spdif_start = 14, + .adat_start = 16, + .mix_start = 24, + + .opt_master = { + .start = -1, + .len = 31, + .names = s18i6_names + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .names = s18i6_names + }, + + .num_controls = 5, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + }, + + .matrix_mux_init = { + 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */ + 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */ + 14, 15, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static const char * const s18i8_names[] = { + TXT_OFF, + TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4), + TXT_PCM(5), TXT_PCM(6), TXT_PCM(7), TXT_PCM(8), + TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4), + TXT_ANALOG(5), TXT_ANALOG(6), TXT_ANALOG(7), TXT_ANALOG(8), + TXT_SPDIF(1), TXT_SPDIF(2), + TXT_ADAT(1), TXT_ADAT(2), TXT_ADAT(3), TXT_ADAT(4), + TXT_ADAT(5), TXT_ADAT(6), TXT_ADAT(7), TXT_ADAT(8), + TXT_MIX(A), TXT_MIX(B), TXT_MIX(C), TXT_MIX(D), + TXT_MIX(E), TXT_MIX(F), TXT_MIX(G), TXT_MIX(H), +}; + +static const struct scarlett_device_info s18i8_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 8, + + .pcm_start = 0, + .analog_start = 8, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 26, + + .opt_master = { + .start = -1, + .len = 35, + .names = s18i8_names + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .names = s18i8_names + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */ + 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */ + 16, 17, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static const char * const s18i20_names[] = { + TXT_OFF, + TXT_PCM(1), TXT_PCM(2), TXT_PCM(3), TXT_PCM(4), + TXT_PCM(5), TXT_PCM(6), TXT_PCM(7), TXT_PCM(8), + TXT_PCM(9), TXT_PCM(10), TXT_PCM(11), TXT_PCM(12), + TXT_PCM(13), TXT_PCM(14), TXT_PCM(15), TXT_PCM(16), + TXT_PCM(17), TXT_PCM(18), TXT_PCM(19), TXT_PCM(20), + TXT_ANALOG(1), TXT_ANALOG(2), TXT_ANALOG(3), TXT_ANALOG(4), + TXT_ANALOG(5), TXT_ANALOG(6), TXT_ANALOG(7), TXT_ANALOG(8), + TXT_SPDIF(1), TXT_SPDIF(2), + TXT_ADAT(1), TXT_ADAT(2), TXT_ADAT(3), TXT_ADAT(4), + TXT_ADAT(5), TXT_ADAT(6), TXT_ADAT(7), TXT_ADAT(8), + TXT_MIX('A'), TXT_MIX('B'), TXT_MIX('C'), TXT_MIX('D'), + TXT_MIX('E'), TXT_MIX('F'), TXT_MIX('G'), TXT_MIX('H'), +}; + +static const struct scarlett_device_info s18i20_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 20, + + .pcm_start = 0, + .analog_start = 20, + .spdif_start = 28, + .adat_start = 30, + .mix_start = 38, + + .opt_master = { + .start = -1, + .len = 47, + .names = s18i20_names + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .names = s18i20_names + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" }, + { .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" }, + { .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" }, + { .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" }, + { .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" }, + { .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" }, + /*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/ + }, + + .matrix_mux_init = { + 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */ + 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */ + 28, 29, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + + +static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, + const struct scarlett_device_info *info) +{ + int i = 0; + int err = 0; + char mx[32]; + const struct scarlett_mixer_control *ctl; + struct usb_mixer_elem_info *elem; + + /* create master switch and playback volume */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, 0, + USB_MIXER_S16, 1, "Master Playback Switch", NULL, + &elem); + if (err < 0) + return err; + + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, 0, + USB_MIXER_S16, 1, "Master Playback Volume", NULL, + &elem); + if (err < 0) + return err; + + /* iterate through controls in info struct and create each one */ + for (i = 0; i < info->num_controls; i++) { + ctl = &info->controls[i]; + + switch (ctl->type) { + case SCARLETT_OUTPUTS: + err = add_output_ctls(mixer, ctl->num, ctl->name, info); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_IMPEDANCE: + sprintf(mx, "Input %d Impedance Switch", + ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x09, ctl->num, USB_MIXER_S16, 1, mx, + &opt_impedance, &elem); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_PAD: + sprintf(mx, "Input %d Pad Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x0b, ctl->num, USB_MIXER_S16, 1, mx, + &opt_pad, &elem); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* + * Create and initialize a mixer for the Focusrite(R) Scarlett + */ +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[32]; + const struct scarlett_device_info *info; + struct usb_mixer_elem_info *elem; + static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8012): + info = &s6i6_info; + break; + case USB_ID(0x1235, 0x8002): + info = &s8i6_info; + break; + case USB_ID(0x1235, 0x8004): + info = &s18i6_info; + break; + case USB_ID(0x1235, 0x8014): + info = &s18i8_info; + break; + case USB_ID(0x1235, 0x800c): + info = &s18i20_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + /* generic function to create controls */ + err = scarlett_controls_create_generic(mixer, info); + if (err < 0) + return err; + + for (i = 0; i < info->matrix_in; i++) { + snprintf(mx, 32, "Matrix %02d Input Playback Route", i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x32, 0x06, i, + USB_MIXER_S16, 1, mx, &info->opt_matrix, + &elem); + if (err < 0) + return err; + + for (o = 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); + err = add_new_ctl(mixer, &usb_scarlett_ctl, 0x3c, 0x00, + (i << 3) + (o & 0x07), USB_MIXER_S16, + 1, mx, NULL, &elem); + if (err < 0) + return err; + + } + } + + for (i = 0; i < info->input_len; i++) { + snprintf(mx, 32, "Input Source %02d Capture Route", i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x34, 0x00, i, + USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + } + + /* val_len == 1 needed here */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0, + USB_MIXER_U8, 1, "Sample Clock Source", + &opt_clock, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_save, 0x3c, 0x00, 0x5a, + USB_MIXER_U8, 1, "Save To HW", &opt_save, &elem); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + 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) | + (0x29 << 8), sample_rate_buffer, 4); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H + +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_H */
At Mon, 3 Nov 2014 16:58:16 -0600, Chris J Arges wrote:
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. The custom scarlett_mixer_elem_info struct was replaced with the more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.
This patch also makes additional modifications based on upstream comments. Individual control creation functions are removed and a generic function is no used. Macros for function calls are removed to improve readability. Hardcoded control initialization is removed.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com
The new patch looks almost good. A remaining concern is:
+static const struct scarlett_mixer_elem_enum_info opt_save = {
- .start = 0,
- .len = 2,
- .names = (const char * const[]){
"---", "Save"
This isn't quite intuitive. And I think this is an abuse of ctl enum (see below).
Also...
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
- int err, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
- if (err < 0)
return err;
- if ((opt->start == -1) && (val > opt->len)) /* >= 0x20 */
val = 0;
- else
val = clamp(val - opt->start, 0, opt->len-1);
Is the if condition above really correct? It's not obvious to me what this really checks.
Now back to "save to hw" ctl:
+static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- ucontrol->value.enumerated.item[0] = 0;
- return 0;
+}
So, here is the problem. You want to use this ctl to trigger something. This is no correct behavior for an enum ctl. If the put callback succeeds, the value should be stored. (Of course, then it won't work as you expected.)
What actually this control does? Why it can't be done always (transparently)?
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0] > 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c << 8), buf, 2);
if (err < 0)
return err;
usb_audio_info(elem->mixer->chip,
"scarlett: saved settings to hardware.\n");
Using *_info() here is rather annoying, it may spew too many random messages.
+static int add_new_ctl(struct usb_mixer_interface *mixer,
const struct snd_kcontrol_new *ncontrol,
int index, int offset, int num,
int val_type, int channels, const char *name,
const struct scarlett_mixer_elem_enum_info *opt,
struct usb_mixer_elem_info **elem_ret
+) +{
- struct snd_kcontrol *kctl;
- struct usb_mixer_elem_info *elem;
- int err;
- elem = kzalloc(sizeof(*elem), GFP_KERNEL);
- if (!elem)
return -ENOMEM;
- elem->mixer = mixer;
- elem->control = offset;
- elem->idx_off = num;
- elem->id = index;
- elem->val_type = val_type;
- elem->channels = channels;
- /* add scarlett_mixer_elem_enum_info struct */
- elem->private_data = (void *)opt;
- kctl = snd_ctl_new1(ncontrol, elem);
- if (!kctl) {
usb_audio_err(mixer->chip, "cannot malloc kcontrol\n");
This is superfluous. kmalloc() already gives warning.
+static int add_output_ctls(struct usb_mixer_interface *mixer,
int index, const char *name,
const struct scarlett_device_info *info)
+{
- int err;
- char mx[48];
Use SNDRV_CTL_ELEM_ID_NAME_MAXLEN (which is 44).
+static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer,
- const struct scarlett_device_info *info)
+{
- int i = 0;
- int err = 0;
- char mx[32];
Ditto. Also, use snprintf() consistently.
+/*
- Create and initialize a mixer for the Focusrite(R) Scarlett
- */
+int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{
- int err, i, o;
- char mx[32];
Ditto.
thanks,
Takashi
+#define TXT_OFF "Off" +#define TXT_PCM(num) "PCM " #num +#define TXT_ANALOG(num) "Analog " #num +#define TXT_SPDIF(num) "SPDIF " #num +#define TXT_ADAT(num) "ADAT " #num +#define TXT_MIX(letter) "MIX " #letter
My reasoning for using: static const char txtOff[] = "Off", txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2", txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4", [...] instead of repeating the strings for each device (or using macros that expand to the same strings) was that the final .o / .ko would contain the string data only once.
But maybe (as Clemens suggested) the strings *for .opt_master and .opt_matrix* should just be created dynamically in scarlett_ctl_enum_info, instead of using snd_ctl_enum_info. I want to point out that scarlett_device_info already contains the necessary information to do this:
.matrix_out = 6, ... .pcm_start = 0, // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12) .analog_start = 12, // analog(1) .. analog(4) .spdif_start = 16, // spdif(1) .. spdif(2) .adat_start = 18, // adat(1) .. adat(0) [i.e. no adat] .mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
and Off(-1). Only elem->private_data has to be of type scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, ...), but of type scarlett_device_info for the master and mixer enums...
Some more comments inline:
On 04/11/14 11:18, Takashi Iwai wrote:
The new patch looks almost good. A remaining concern is:
+static const struct scarlett_mixer_elem_enum_info opt_save = {
- .start = 0,
- .len = 2,
- .names = (const char * const[]){
"---", "Save"
This isn't quite intuitive. And I think this is an abuse of ctl enum (see below).
It's a hack to expose the "Save to hardware" functionality without requiring a special mixer application. Robin's original patch already contained this (ab)use.
Also...
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
- int err, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0,&val);
- if (err< 0)
return err;
- if ((opt->start == -1)&& (val> opt->len)) /*>= 0x20 */
val = 0;
- else
val = clamp(val - opt->start, 0, opt->len-1);
Is the if condition above really correct? It's not obvious to me what this really checks.
(opt->start == -1) is used in enums that control the routing, to represent the "Off"-value. But the device uses number_of_master_enum_values+1 (even for the matrix enum) as "Off". That would make "Off" the last enum value in Mixer applications, which is undesired (and, for the matrix enums, this still results in a gap between last_matrix_enum_index and Off=last_mixer_enum_index).
scarlett_ctl_enum_put did contain the inverse logic (before cleanup):
#if 0 // TODO? if (val == -1) { val = elem->enum->len + 1; /* only true for master, not for mixer [also master must be used] */ // ... or? > 0x20, 18i8: 0x22 } else #endif val = val + elem->opt->start;
It is commented out, because the device also recognized val = -1 + -1; as "Off", without the need to somehow expose the device-specific enum->len of .opt_master to all the .opt_matrix controls. It's a bit dirty, but it works...
Now back to "save to hw" ctl:
+static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- ucontrol->value.enumerated.item[0] = 0;
- return 0;
+}
So, here is the problem. You want to use this ctl to trigger something. This is no correct behavior for an enum ctl. If the put callback succeeds, the value should be stored. (Of course, then it won't work as you expected.)
What actually this control does? Why it can't be done always (transparently)?
What it does is it store the current settings in non-volatile memory for standalone operation (i.e. USB not connected). It's the device configuration after power-on.
Loading this driver then reinitalizes the device to basically zero values (userspace "acontrol restore" will finally set the device to what the user expects). The zeroing is unfortunately necessary, because the device does not support reading back certain mixer values after power-on (only garbage is returned). After initialization, only cval->cache_val is ever returned by snd_usb_get_cur_mix_value.
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0]> 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c<< 8), buf, 2);
if (err< 0)
return err;
usb_audio_info(elem->mixer->chip,
"scarlett: saved settings to hardware.\n");
Using *_info() here is rather annoying, it may spew too many random messages.
OTOH, saving the settings to hardware should be a rare operation (the non-volatile memory might also be designed to withstand only a limited amount of read-write cycles).
@Chris: The original get_ctl_value (after cleanup: snd_usb_get_cur_mix_value) contained this:
// quirk: write 2bytes, but read 1byte if ( (elem->index == 0x01)|| // input impedance and input
pad switch ((elem->index == 0x0a)&&(elem->wValue < 0x0200))|| // bus mutes (elem->index == 0x32)||(elem->index == 0x33) ) { // mux val_len = 1; }
Is it removed(AFAICT) intentionally in your patches? The windows mixer software did read-back some values (but only after they had been written once, IIRC), and had this asymmetry that certain registers used 2-byte writes but 1-byte reads... I'm quite unsure about just reading 2 bytes, i.e. whether the device would actually return the correct values. OTOH, we should only hit the cache because of the zero-initialization(?).
Tobias
On 04/11/14 14:16, Tobias Hoffmann wrote:
But maybe (as Clemens suggested) the strings *for .opt_master and .opt_matrix* should just be created dynamically in scarlett_ctl_enum_info, instead of using snd_ctl_enum_info. I want to point out that scarlett_device_info already contains the necessary information to do this:
.matrix_out = 6,
... .pcm_start = 0, // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12) .analog_start = 12, // analog(1) .. analog(4) .spdif_start = 16, // spdif(1) .. spdif(2) .adat_start = 18, // adat(1) .. adat(0) [i.e. no adat] .mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
and Off(-1). Only elem->private_data has to be of type scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, ...), but of type scarlett_device_info for the master and mixer enums...
Hmm, as the same options are required for a lot of master and mixer enums, the strings should probably be generated only once (and a dynamic scarlett_mixer_elem_enum_info would require no modifications of scarlett_ctl_enum_info).
Tobias
On Tue, Nov 04, 2014 at 02:29:02PM +0100, Tobias Hoffmann wrote:
On 04/11/14 14:16, Tobias Hoffmann wrote:
But maybe (as Clemens suggested) the strings *for .opt_master and .opt_matrix* should just be created dynamically in scarlett_ctl_enum_info, instead of using snd_ctl_enum_info. I want to point out that scarlett_device_info already contains the necessary information to do this:
.matrix_out = 6, ... .pcm_start = 0, // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12) .analog_start = 12, // analog(1) .. analog(4) .spdif_start = 16, // spdif(1) .. spdif(2) .adat_start = 18, // adat(1) .. adat(0) [i.e. no adat] .mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
and Off(-1). Only elem->private_data has to be of type scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, ...), but of type scarlett_device_info for the master and mixer enums...
Hmm, as the same options are required for a lot of master and mixer enums, the strings should probably be generated only once (and a dynamic scarlett_mixer_elem_enum_info would require no modifications of scarlett_ctl_enum_info).
Tobias
Yes this would be the plan dynamially generate them once and reuse them. I'll work on implementing this. --chris
At Tue, 04 Nov 2014 14:16:13 +0100, Tobias Hoffmann wrote:
+#define TXT_OFF "Off" +#define TXT_PCM(num) "PCM " #num +#define TXT_ANALOG(num) "Analog " #num +#define TXT_SPDIF(num) "SPDIF " #num +#define TXT_ADAT(num) "ADAT " #num +#define TXT_MIX(letter) "MIX " #letter
My reasoning for using: static const char txtOff[] = "Off", txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2", txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4", [...] instead of repeating the strings for each device (or using macros that expand to the same strings) was that the final .o / .ko would contain the string data only once.
The compiler should be clever enough.
But maybe (as Clemens suggested) the strings *for .opt_master and .opt_matrix* should just be created dynamically in scarlett_ctl_enum_info, instead of using snd_ctl_enum_info. I want to point out that scarlett_device_info already contains the necessary information to do this:
.matrix_out = 6, ... .pcm_start = 0, // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12) .analog_start = 12, // analog(1) .. analog(4) .spdif_start = 16, // spdif(1) .. spdif(2) .adat_start = 18, // adat(1) .. adat(0) [i.e. no adat] .mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
and Off(-1). Only elem->private_data has to be of type scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, ...), but of type scarlett_device_info for the master and mixer enums...
Yeah, that would be also cleaner.
Some more comments inline:
On 04/11/14 11:18, Takashi Iwai wrote:
The new patch looks almost good. A remaining concern is:
+static const struct scarlett_mixer_elem_enum_info opt_save = {
- .start = 0,
- .len = 2,
- .names = (const char * const[]){
"---", "Save"
This isn't quite intuitive. And I think this is an abuse of ctl enum (see below).
It's a hack to expose the "Save to hardware" functionality without requiring a special mixer application. Robin's original patch already contained this (ab)use.
Heh, the history can't be an excuse to continue the abuse :)
Also...
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
- int err, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0,&val);
- if (err< 0)
return err;
- if ((opt->start == -1)&& (val> opt->len)) /*>= 0x20 */
val = 0;
- else
val = clamp(val - opt->start, 0, opt->len-1);
Is the if condition above really correct? It's not obvious to me what this really checks.
(opt->start == -1) is used in enums that control the routing, to represent the "Off"-value. But the device uses number_of_master_enum_values+1 (even for the matrix enum) as "Off".
That would make "Off" the last enum value in Mixer applications, which is undesired (and, for the matrix enums, this still results in a gap between last_matrix_enum_index and Off=last_mixer_enum_index).
scarlett_ctl_enum_put did contain the inverse logic (before cleanup):
#if 0 // TODO? if (val == -1) { val = elem->enum->len + 1; /* only true for master, not for mixer [also master must be used] */ // ... or? > 0x20, 18i8: 0x22 } else #endif val = val + elem->opt->start;
It is commented out, because the device also recognized val = -1 + -1; as "Off", without the need to somehow expose the device-specific enum->len of .opt_master to all the .opt_matrix controls. It's a bit dirty, but it works...
Oh, no this is too tricky. If you want to keep this coding, you have to put this whole comment in the code. Otherwise no reader understands the code correctly.
Please, don't play with too much tricks. This is no hot path. The code size and speed don't matter so much. The readability has to be cared at most, instead.
Now back to "save to hw" ctl:
+static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- ucontrol->value.enumerated.item[0] = 0;
- return 0;
+}
So, here is the problem. You want to use this ctl to trigger something. This is no correct behavior for an enum ctl. If the put callback succeeds, the value should be stored. (Of course, then it won't work as you expected.)
What actually this control does? Why it can't be done always (transparently)?
What it does is it store the current settings in non-volatile memory for standalone operation (i.e. USB not connected). It's the device configuration after power-on.
Loading this driver then reinitalizes the device to basically zero values (userspace "acontrol restore" will finally set the device to what the user expects). The zeroing is unfortunately necessary, because the device does not support reading back certain mixer values after power-on (only garbage is returned). After initialization, only cval->cache_val is ever returned by snd_usb_get_cur_mix_value.
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0]> 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c<< 8), buf, 2);
if (err< 0)
return err;
usb_audio_info(elem->mixer->chip,
"scarlett: saved settings to hardware.\n");
Using *_info() here is rather annoying, it may spew too many random messages.
OTOH, saving the settings to hardware should be a rare operation (the non-volatile memory might also be designed to withstand only a limited amount of read-write cycles).
If so, implementing it as a mixer ctl is a wrong design. I'd suggest rather to drop whole this stuff at first. If really inevitably needed, you can implement it in a different way.
thanks,
Takashi
On Tue, Nov 04, 2014 at 03:00:46PM +0100, Takashi Iwai wrote:
At Tue, 04 Nov 2014 14:16:13 +0100, Tobias Hoffmann wrote:
+#define TXT_OFF "Off" +#define TXT_PCM(num) "PCM " #num +#define TXT_ANALOG(num) "Analog " #num +#define TXT_SPDIF(num) "SPDIF " #num +#define TXT_ADAT(num) "ADAT " #num +#define TXT_MIX(letter) "MIX " #letter
My reasoning for using: static const char txtOff[] = "Off", txtPcm1[] = "PCM 1", txtPcm2[] = "PCM 2", txtPcm3[] = "PCM 3", txtPcm4[] = "PCM 4", [...] instead of repeating the strings for each device (or using macros that expand to the same strings) was that the final .o / .ko would contain the string data only once.
The compiler should be clever enough.
But maybe (as Clemens suggested) the strings *for .opt_master and .opt_matrix* should just be created dynamically in scarlett_ctl_enum_info, instead of using snd_ctl_enum_info. I want to point out that scarlett_device_info already contains the necessary information to do this:
.matrix_out = 6, ... .pcm_start = 0, // pcm(1) .. pcm(analog_start-pcm_start)=pcm(12) .analog_start = 12, // analog(1) .. analog(4) .spdif_start = 16, // spdif(1) .. spdif(2) .adat_start = 18, // adat(1) .. adat(0) [i.e. no adat] .mix_start = 18, // mix('A') .. mix('F')=mix('A' + matrix_out)
and Off(-1). Only elem->private_data has to be of type scarlett_mixer_elem_enum_info for the usual enums (impedance, pad, ...), but of type scarlett_device_info for the master and mixer enums...
Yeah, that would be also cleaner.
Some more comments inline:
On 04/11/14 11:18, Takashi Iwai wrote:
The new patch looks almost good. A remaining concern is:
+static const struct scarlett_mixer_elem_enum_info opt_save = {
- .start = 0,
- .len = 2,
- .names = (const char * const[]){
"---", "Save"
This isn't quite intuitive. And I think this is an abuse of ctl enum (see below).
It's a hack to expose the "Save to hardware" functionality without requiring a special mixer application. Robin's original patch already contained this (ab)use.
Heh, the history can't be an excuse to continue the abuse :)
Also...
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
- int err, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0,&val);
- if (err< 0)
return err;
- if ((opt->start == -1)&& (val> opt->len)) /*>= 0x20 */
val = 0;
- else
val = clamp(val - opt->start, 0, opt->len-1);
Is the if condition above really correct? It's not obvious to me what this really checks.
(opt->start == -1) is used in enums that control the routing, to represent the "Off"-value. But the device uses number_of_master_enum_values+1 (even for the matrix enum) as "Off".
That would make "Off" the last enum value in Mixer applications, which is undesired (and, for the matrix enums, this still results in a gap between last_matrix_enum_index and Off=last_mixer_enum_index).
scarlett_ctl_enum_put did contain the inverse logic (before cleanup):
#if 0 // TODO? if (val == -1) { val = elem->enum->len + 1; /* only true for master, not for mixer [also master must be used] */ // ... or? > 0x20, 18i8: 0x22 } else #endif val = val + elem->opt->start;
It is commented out, because the device also recognized val = -1 + -1; as "Off", without the need to somehow expose the device-specific enum->len of .opt_master to all the .opt_matrix controls. It's a bit dirty, but it works...
Oh, no this is too tricky. If you want to keep this coding, you have to put this whole comment in the code. Otherwise no reader understands the code correctly.
Please, don't play with too much tricks. This is no hot path. The code size and speed don't matter so much. The readability has to be cared at most, instead.
Now back to "save to hw" ctl:
+static int scarlett_ctl_save_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- ucontrol->value.enumerated.item[0] = 0;
- return 0;
+}
So, here is the problem. You want to use this ctl to trigger something. This is no correct behavior for an enum ctl. If the put callback succeeds, the value should be stored. (Of course, then it won't work as you expected.)
What actually this control does? Why it can't be done always (transparently)?
What it does is it store the current settings in non-volatile memory for standalone operation (i.e. USB not connected). It's the device configuration after power-on.
Loading this driver then reinitalizes the device to basically zero values (userspace "acontrol restore" will finally set the device to what the user expects). The zeroing is unfortunately necessary, because the device does not support reading back certain mixer values after power-on (only garbage is returned). After initialization, only cval->cache_val is ever returned by snd_usb_get_cur_mix_value.
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0]> 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c<< 8), buf, 2);
if (err< 0)
return err;
usb_audio_info(elem->mixer->chip,
"scarlett: saved settings to hardware.\n");
Using *_info() here is rather annoying, it may spew too many random messages.
OTOH, saving the settings to hardware should be a rare operation (the non-volatile memory might also be designed to withstand only a limited amount of read-write cycles).
If so, implementing it as a mixer ctl is a wrong design. I'd suggest rather to drop whole this stuff at first. If really inevitably needed, you can implement it in a different way.
thanks,
Takashi
Takashi, I'll drop it for the initial patchset. Clemens mentioned implementing it as a mixer control that allows no access but a TLV_COMMAND. Perhaps that could be a start and then writing support in any userspace applications to expose that 'button' control correctly.
--chris
On Tue, Nov 04, 2014 at 02:16:13PM +0100, Tobias Hoffmann wrote: <snip>
@Chris: The original get_ctl_value (after cleanup: snd_usb_get_cur_mix_value) contained this:
// quirk: write 2bytes, but read 1byte if ( (elem->index == 0x01)|| // input impedance and
input pad switch ((elem->index == 0x0a)&&(elem->wValue < 0x0200))|| // bus mutes (elem->index == 0x32)||(elem->index == 0x33) ) { // mux val_len = 1; }
Is it removed(AFAICT) intentionally in your patches? The windows mixer software did read-back some values (but only after they had been written once, IIRC), and had this asymmetry that certain registers used 2-byte writes but 1-byte reads... I'm quite unsure about just reading 2 bytes, i.e. whether the device would actually return the correct values. OTOH, we should only hit the cache because of the zero-initialization(?).
Tobias
Tobias, I've notcied that the controls work just fine without this hack (at least on my 18i8); however I can look more closely into this to see what the extra byte that's being read actually is. --chris
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. The custom scarlett_mixer_elem_info struct was replaced with the more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.
This patch also makes additional modifications based on upstream comments. Individual control creation functions are removed and a generic function is no used. Macros for function calls are removed to improve readability. Hardcoded control initialization is removed. Save to HW functionality has been removed. Strings for enums are created dynamically for the mixer. Strings used for controls are now SNDRV_CTL_ELEM_ID_NAME_MAXLEN length.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/Makefile | 1 + sound/usb/mixer_quirks.c | 9 + sound/usb/mixer_scarlett.c | 1009 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + 4 files changed, 1025 insertions(+) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..bcee406 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -9,6 +9,7 @@ snd-usb-audio-objs := card.o \ helper.o \ mixer.o \ mixer_quirks.o \ + mixer_scarlett.o \ pcm.o \ proc.o \ quirks.o \ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index c9665bf..d06d27a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -41,6 +41,7 @@ #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" +#include "mixer_scarlett.h" #include "helper.h"
extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; @@ -1664,6 +1665,14 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) /* detection is disabled in mixer_maps.c */ err = snd_create_std_mono_table(mixer, ebox44_table); break; + + case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */ + case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */ + case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */ + case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */ + case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */ + err = snd_scarlett_controls_create(mixer); + break; }
return err; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c new file mode 100644 index 0000000..7a23083 --- /dev/null +++ b/sound/usb/mixer_scarlett.c @@ -0,0 +1,1009 @@ +/* + * Scarlett 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Rewritten and extended to support more models, e.g. Scarlett 18i8. + * + * Many features of Scarlett 18i6 do not lend themselves to be implemented + * as simple mixer-quirk -- or at least I don't see a way how to do that, yet. + * Hence the top parts of this file is a 1:1 copy of select static functions + * from mixer.c to implement the interface. + * Suggestions how to avoid this code duplication are very welcome. + * + * eventually this should either be integrated as quirk into mixer_quirks.c + * or become a standalone module. + * + * This source hardcodes the URBs for the Scarlett, + * 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. + * + * NB. Neither the OSX nor the win driver provided by Focusrite performs + * discovery, they seem to operate the same as this driver. + */ + +/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface. + * + * The protocol was reverse engineered by looking at communication between + * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 + * (firmware v305) using wireshark and usbmon in January 2013. + * Extended in July 2013. + * + * this mixer gives complete access to all features of the device: + * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z) + * - select clock source + * - dynamic input to mixer-matrix assignment + * - 18 x 6 mixer-matrix gain stages + * - bus routing & volume control + * - save setting to hardware + * - automatic re-initialization on connect if device was power-cycled + * - peak monitoring of all 3 buses (18 input, 6 DAW input, 6 route channels) + * (changing the samplerate and buffersize is supported by the PCM interface) + * + * + * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR) + * wIndex + * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + + * channel, data=Line/Inst (2bytes) + * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes) + * ?? wValue=0x0803/04, ?? (2bytes) + * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes) + * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes) + * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte) + * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes) + * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes) + * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes) + * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes) + * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes) + * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff) + * + * USB reads: (i.e. actually issued by original software) + * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!) + * 0x29 wValue=0x0100 sample-rate(4bytes) + * wValue=0x0200 ?? 1byte (only once) + * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ?? + * + * USB reads with bRequest = 0x03 = UAC2_CS_MEM + * 0x3c wValue=0x0002 1byte: sync status (locked=1) + * wValue=0x0000 18*2byte: peak meter (inputs) + * wValue=0x0001 8(?)*2byte: peak meter (mix) + * wValue=0x0003 6*2byte: peak meter (pcm/daw) + * + * USB write with bRequest = 0x03 + * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5 + * + * + * <ditaa> + * /--------------\ 18chn 6chn /--------------\ + * | Hardware in +--+-------\ /------+--+ ALSA PCM out | + * --------------/ | | | | --------------/ + * | | | | + * | v v | + * | +---------------+ | + * | \ Matrix Mux / | + * | +-----+-----+ | + * | | | + * | | 18chn | + * | v | + * | +-----------+ | + * | | Mixer | | + * | | Matrix | | + * | | | | + * | | 18x6 Gain | | + * | | stages | | + * | +-----+-----+ | + * | | | + * | | | + * | 18chn | 6chn | 6chn + * v v v + * ========================= + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +-----+-----+ +-----+-----+ + * | | + * | 6chn | + * v | + * +-------------+ | + * | Master Gain | | + * +------+------+ | + * | | + * | 6chn | 18chn + * | (3 stereo pairs) | + * /--------------\ | | /--------------\ + * | Hardware out |<--/ -->| ALSA PCM in | + * --------------/ --------------/ + * </ditaa> + * + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "power.h" + +#include "mixer_scarlett.h" + +/* some gui mixers can't handle negative ctl values */ +#define SND_SCARLETT_LEVEL_BIAS 128 +#define SND_SCARLETT_MATRIX_IN_MAX 18 +#define SND_SCARLETT_CONTROLS_MAX 10 + +enum { + SCARLETT_OUTPUTS, + SCARLETT_SWITCH_IMPEDANCE, + SCARLETT_SWITCH_PAD, +}; + +struct scarlett_mixer_elem_enum_info { + int start; + int len; + char **names; +}; + +struct scarlett_mixer_control { + unsigned char num; + unsigned char type; + const char *name; +}; + +struct scarlett_device_info { + int matrix_in; + int matrix_out; + int input_len; + int output_len; + + int pcm_start; + int analog_start; + int spdif_start; + int adat_start; + int mix_start; + + struct scarlett_mixer_elem_enum_info opt_master; + struct scarlett_mixer_elem_enum_info opt_matrix; + + int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX]; + + int num_controls; /* number of items in controls */ + const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX]; +}; + +int scarlett_init = 0; + +/********************** Enum Strings *************************/ + +static const struct scarlett_mixer_elem_enum_info opt_pad = { + .start = 0, + .len = 2, + .names = (char *[]){ + "0dB", "-10dB" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_impedance = { + .start = 0, + .len = 2, + .names = (char *[]){ + "Line", "Hi-Z" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_clock = { + .start = 1, + .len = 3, + .names = (char *[]){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_sync = { + .start = 0, + .len = 2, + .names = (char *[]){ + "No Lock", "Locked" + } +}; + +static int scarlett_ctl_switch_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_BOOLEAN; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = !val; /* invert mute logic for mixer */ + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_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 = (int)kctl->private_value + + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + + SND_SCARLETT_LEVEL_BIAS; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - + SND_SCARLETT_LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + + return snd_ctl_enum_info(uinfo, elem->channels, opt->len, + (const char * const *)opt->names); +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &val); + if (err < 0) + return err; + + if ((opt->start == -1) && (val > opt->len)) /* >= 0x20 */ + val = 0; + else + val = clamp(val - opt->start, 0, opt->len-1); + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int changed = 0; + int err, oval, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + opt->start; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, 0, 0, val); + if (err < 0) + return err; + + changed = 1; + } + + return changed; +} + +static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->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->id << 8); + int err; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, elem->channels); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + return 0; +} + +static struct snd_kcontrol_new usb_scarlett_ctl_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_switch_info, + .get = scarlett_ctl_switch_get, + .put = scarlett_ctl_switch_put, +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); + +static struct snd_kcontrol_new usb_scarlett_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_master = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_sync = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_meter_get, +}; + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int offset, int num, + int val_type, int channels, const char *name, + const struct scarlett_mixer_elem_enum_info *opt, + struct usb_mixer_elem_info **elem_ret +) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->mixer = mixer; + elem->control = offset; + elem->idx_off = num; + elem->id = index; + elem->val_type = val_type; + + elem->channels = channels; + + /* add scarlett_mixer_elem_enum_info struct */ + elem->private_data = (void *)opt; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name); + + err = snd_ctl_add(mixer->chip->card, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +static int add_output_ctls(struct usb_mixer_interface *mixer, + int index, const char *name, + const struct scarlett_device_info *info) +{ + int err; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct usb_mixer_elem_info *elem; + + /* Add mute switch */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add volume control and initialize to 0 */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index, + USB_MIXER_S16, 1, mx, &info->opt_master, &elem); + if (err < 0) + return err; + + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index+1, + USB_MIXER_S16, 1, mx, &info->opt_master, &elem); + if (err < 0) + return err; + + return 0; +} + +/********************** device-specific config *************************/ + +/* untested... */ +static struct scarlett_device_info s6i6_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 6, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 27, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = NULL + }, + + .num_controls = 0, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +/* untested... */ +static struct scarlett_device_info s8i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 8, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 25, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = NULL + }, + + .num_controls = 7, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +static struct scarlett_device_info s18i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 18, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 6, + .spdif_start = 14, + .adat_start = 16, + .mix_start = 24, + + .opt_master = { + .start = -1, + .len = 31, + .names = NULL, + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .names = NULL, + }, + + .num_controls = 5, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + }, + + .matrix_mux_init = { + 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */ + 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */ + 14, 15, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static struct scarlett_device_info s18i8_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 8, + + .pcm_start = 0, + .analog_start = 8, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 26, + + .opt_master = { + .start = -1, + .len = 35, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */ + 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */ + 16, 17, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static struct scarlett_device_info s18i20_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 20, + + .pcm_start = 0, + .analog_start = 20, + .spdif_start = 28, + .adat_start = 30, + .mix_start = 38, + + .opt_master = { + .start = -1, + .len = 47, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" }, + { .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" }, + { .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" }, + { .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" }, + { .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" }, + { .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" }, + /*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/ + }, + + .matrix_mux_init = { + 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */ + 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */ + 28, 29, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + + +static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, + struct scarlett_device_info *info) +{ + int i = 0; + int err = 0; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const struct scarlett_mixer_control *ctl; + struct usb_mixer_elem_info *elem; + + /* create master switch and playback volume */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, 0, + USB_MIXER_S16, 1, "Master Playback Switch", NULL, + &elem); + if (err < 0) + return err; + + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, 0, + USB_MIXER_S16, 1, "Master Playback Volume", NULL, + &elem); + if (err < 0) + return err; + + /* iterate through controls in info struct and create each one */ + for (i = 0; i < info->num_controls; i++) { + ctl = &info->controls[i]; + + switch (ctl->type) { + case SCARLETT_OUTPUTS: + err = add_output_ctls(mixer, ctl->num, ctl->name, info); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_IMPEDANCE: + sprintf(mx, "Input %d Impedance Switch", + ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x09, ctl->num, USB_MIXER_S16, 1, mx, + &opt_impedance, &elem); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_PAD: + sprintf(mx, "Input %d Pad Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x0b, ctl->num, USB_MIXER_S16, 1, mx, + &opt_pad, &elem); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* + * Create enumeration strings from device_info + */ +int scarlett_create_strings_from_info(struct scarlett_device_info *info) +{ + int i = 0; + char **names; + + names = kmalloc_array(info->opt_master.len, sizeof(char *), GFP_KERNEL); + if (!names) { + kfree(names); + return -ENOMEM; + } + + for (i = 0; i < info->opt_master.len; i++) { + names[i] = kmalloc_array(SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + sizeof(char), GFP_KERNEL); + if (!names[i]) { + kfree(names); + return -ENOMEM; + } + if (i > info->mix_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Mix %c", 'A'+(i - info->mix_start - 1)); + } else if (i > info->adat_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "ADAT %d", i - info->adat_start); + } else if (i > info->spdif_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "SPDIF %d", i - info->spdif_start); + } else if (i > info->analog_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Analog %d", i - info->analog_start); + } else if (i > info->pcm_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "PCM %d", i - info->pcm_start); + } else { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Off"); + } + } + + /* assign to the appropriate control */ + info->opt_master.names = names; + info->opt_matrix.names = names; + + return 0; +} + +/* + * Create and initialize a mixer for the Focusrite(R) Scarlett + */ +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct scarlett_device_info *info; + struct usb_mixer_elem_info *elem; + static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8012): + info = &s6i6_info; + break; + case USB_ID(0x1235, 0x8002): + info = &s8i6_info; + break; + case USB_ID(0x1235, 0x8004): + info = &s18i6_info; + break; + case USB_ID(0x1235, 0x8014): + info = &s18i8_info; + break; + case USB_ID(0x1235, 0x800c): + info = &s18i20_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + /* generate strings dynamically for each control */ + err = scarlett_create_strings_from_info(info); + if (err < 0) + return err; + + /* generic function to create controls */ + err = scarlett_controls_create_generic(mixer, info); + if (err < 0) + return err; + + /* setup matrix controls */ + for (i = 0; i < info->matrix_in; i++) { + snprintf(mx, sizeof(mx), "Matrix %02d Input Playback Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x32, 0x06, i, + USB_MIXER_S16, 1, mx, &info->opt_matrix, + &elem); + if (err < 0) + return err; + + for (o = 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); + err = add_new_ctl(mixer, &usb_scarlett_ctl, 0x3c, 0x00, + (i << 3) + (o & 0x07), USB_MIXER_S16, + 1, mx, NULL, &elem); + if (err < 0) + return err; + + } + } + + for (i = 0; i < info->input_len; i++) { + snprintf(mx, sizeof(mx), "Input Source %02d Capture Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x34, 0x00, i, + USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + } + + /* val_len == 1 needed here */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0, + USB_MIXER_U8, 1, "Sample Clock Source", + &opt_clock, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + 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) | + (0x29 << 8), sample_rate_buffer, 4); + if (err < 0) + return err; + + return err; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H + +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_H */
This is v5 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
[v3]
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
[v4]
This version removes the per-mixer control creation functions and uses a generic function based on structure data. Macros used for control addition are removed and the plain function is used instead. Hardcoded text block is removed and macros to define strings are used instead. Hardcoded control initialization has been removed.
[v5]
In this version, HW saving functionality has been removed in this initial patchset. Macros for function calls are removed for readability. Strings for enums are created dynamically using the info structures. String lengths for controls are now all SNDRV_CTL_ELEM_ID_NAME_MAXLEN in length.
Note: Resending the entire patchset because of mail filter issues.
Chris J Arges (4): Revert "ALSA: usb-audio: Add quirk for Focusrite Scarlett ALSA: usb-audio: Add private_data pointer to usb_mixer_elem_info ALSA: usb-audio: make set_*_mix_values functions public ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
sound/usb/Makefile | 1 + sound/usb/mixer.c | 34 +- sound/usb/mixer.h | 9 + sound/usb/mixer_quirks.c | 18 +- sound/usb/mixer_scarlett.c | 1009 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + sound/usb/quirks-table.h | 51 --- 7 files changed, 1052 insertions(+), 76 deletions(-) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
This reverts commit 1762a59d8e8b5e99f6f4a0f292b40f3cacb108ba.
This quirk is not needed because support for the Scarlett mixers will be added.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/quirks-table.h | 51 ------------------------------------------------ 1 file changed, 51 deletions(-)
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index c657752..51686c7 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2667,57 +2667,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), .type = QUIRK_MIDI_NOVATION } }, -{ - /* - * Focusrite Scarlett 18i6 - * - * Avoid mixer creation, which otherwise fails because some of - * the interface descriptor subtypes for interface 0 are - * unknown. That should be fixed or worked-around but this at - * least allows the device to be used successfully with a DAW - * and an external mixer. See comments below about other - * ignored interfaces. - */ - USB_DEVICE(0x1235, 0x8004), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Focusrite", - .product_name = "Scarlett 18i6", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Device Firmware Update) */ - .ifnum = 5, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } - } - } -},
/* Access Music devices */ {
Add a private_data pointer to usb_mixer_elem_info to allow other mixer implementations to extend the structure as necessary.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 73b1f64..8df9a73 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -53,6 +53,7 @@ struct usb_mixer_elem_info { int cached; int cache_val[MAX_CHANNELS]; u8 initialized; + void *private_data; };
int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
Make the functions set_cur_mix_value and get_cur_mix_value accessible by files that include mixer.h. In addition make usb_mixer_elem_free accessible. This allows reuse of these functions by mixers that may require quirks.
The following summarizes the renamed functions: - set_cur_mix_value -> snd_usb_set_cur_mix_value - get_cur_mix_value -> snd_usb_get_cur_mix_value - usb_mixer_elem_free -> snd_usb_mixer_elem_free
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.c | 34 +++++++++++++++++----------------- sound/usb/mixer.h | 8 ++++++++ sound/usb/mixer_quirks.c | 9 +-------- 3 files changed, 26 insertions(+), 25 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 2e4a9db..6b169fb 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -412,7 +412,7 @@ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, value); }
-static int get_cur_mix_value(struct usb_mixer_elem_info *cval, +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int *value) { int err; @@ -497,7 +497,7 @@ static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); }
-static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, +int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int value) { int err; @@ -815,7 +815,7 @@ static struct usb_feature_control_info audio_feature_info[] = { };
/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) +void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl) { kfree(kctl->private_data); kctl->private_data = NULL; @@ -998,7 +998,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, else test -= cval->res; if (test < cval->min || test > cval->max || - set_cur_mix_value(cval, minchn, 0, test) || + snd_usb_set_cur_mix_value(cval, minchn, 0, test) || get_cur_mix_raw(cval, minchn, &check)) { cval->res = last_valid_res; break; @@ -1007,7 +1007,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; cval->res *= 2; } - set_cur_mix_value(cval, minchn, 0, saved); + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); }
cval->initialized = 1; @@ -1086,7 +1086,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &val); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1096,7 +1096,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, return 0; } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &val); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1118,26 +1118,26 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &oval); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[cnt]; val = get_abs_value(cval, val); if (oval != val) { - set_cur_mix_value(cval, c + 1, cnt, val); + snd_usb_set_cur_mix_value(cval, c + 1, cnt, val); changed = 1; } cnt++; } } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &oval); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[0]; val = get_abs_value(cval, val); if (val != oval) { - set_cur_mix_value(cval, 0, 0, val); + snd_usb_set_cur_mix_value(cval, 0, 0, val); changed = 1; } } @@ -1250,7 +1250,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
/* * If all channels in the mask are marked read-only, make the control - * read-only. set_cur_mix_value() will check the mask again and won't + * read-only. snd_usb_set_cur_mix_value() will check the mask again and won't * issue write commands to read-only channels. */ if (cval->channels == readonly_mask) @@ -1263,7 +1263,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; @@ -1547,7 +1547,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (!len) @@ -1847,7 +1847,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, kfree(cval); return -ENOMEM; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) { /* nothing */ ; @@ -2530,7 +2530,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) if (!(cval->cmask & (1 << c))) continue; if (cval->cached & (1 << c)) { - err = set_cur_mix_value(cval, c + 1, idx, + err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) return err; @@ -2540,7 +2540,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) } else { /* master */ if (cval->cached) { - err = set_cur_mix_value(cval, 0, 0, *cval->cache_val); + err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); if (err < 0) return err; } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 8df9a73..8db2980 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -76,4 +76,12 @@ 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_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, + int index, int value); + +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, + int channel, int index, int *value); + +extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl); + #endif /* __USBMIXER_H */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index f119a41..c9665bf 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -52,13 +52,6 @@ struct std_mono_table { snd_kcontrol_tlv_rw_t *tlv_callback; };
-/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) -{ - kfree(kctl->private_data); - kctl->private_data = NULL; -} - /* This function allows for the creation of standard UAC controls. * See the quirks for M-Audio FTUs or Ebox-44. * If you don't want to set a TLV callback pass NULL. @@ -108,7 +101,7 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer,
/* Set name */ snprintf(kctl->id.name, sizeof(kctl->id.name), name); - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
/* set TLV */ if (tlv_callback) {
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. The custom scarlett_mixer_elem_info struct was replaced with the more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.
This patch also makes additional modifications based on upstream comments. Individual control creation functions are removed and a generic function is no used. Macros for function calls are removed to improve readability. Hardcoded control initialization is removed. Save to HW functionality has been removed. Strings for enums are created dynamically for the mixer. Strings used for controls are now SNDRV_CTL_ELEM_ID_NAME_MAXLEN length.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/Makefile | 1 + sound/usb/mixer_quirks.c | 9 + sound/usb/mixer_scarlett.c | 1009 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + 4 files changed, 1025 insertions(+) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..bcee406 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -9,6 +9,7 @@ snd-usb-audio-objs := card.o \ helper.o \ mixer.o \ mixer_quirks.o \ + mixer_scarlett.o \ pcm.o \ proc.o \ quirks.o \ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index c9665bf..d06d27a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -41,6 +41,7 @@ #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" +#include "mixer_scarlett.h" #include "helper.h"
extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; @@ -1664,6 +1665,14 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) /* detection is disabled in mixer_maps.c */ err = snd_create_std_mono_table(mixer, ebox44_table); break; + + case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */ + case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */ + case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */ + case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */ + case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */ + err = snd_scarlett_controls_create(mixer); + break; }
return err; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c new file mode 100644 index 0000000..7a23083 --- /dev/null +++ b/sound/usb/mixer_scarlett.c @@ -0,0 +1,1009 @@ +/* + * Scarlett 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Rewritten and extended to support more models, e.g. Scarlett 18i8. + * + * Many features of Scarlett 18i6 do not lend themselves to be implemented + * as simple mixer-quirk -- or at least I don't see a way how to do that, yet. + * Hence the top parts of this file is a 1:1 copy of select static functions + * from mixer.c to implement the interface. + * Suggestions how to avoid this code duplication are very welcome. + * + * eventually this should either be integrated as quirk into mixer_quirks.c + * or become a standalone module. + * + * This source hardcodes the URBs for the Scarlett, + * 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. + * + * NB. Neither the OSX nor the win driver provided by Focusrite performs + * discovery, they seem to operate the same as this driver. + */ + +/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface. + * + * The protocol was reverse engineered by looking at communication between + * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 + * (firmware v305) using wireshark and usbmon in January 2013. + * Extended in July 2013. + * + * this mixer gives complete access to all features of the device: + * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z) + * - select clock source + * - dynamic input to mixer-matrix assignment + * - 18 x 6 mixer-matrix gain stages + * - bus routing & volume control + * - save setting to hardware + * - automatic re-initialization on connect if device was power-cycled + * - peak monitoring of all 3 buses (18 input, 6 DAW input, 6 route channels) + * (changing the samplerate and buffersize is supported by the PCM interface) + * + * + * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR) + * wIndex + * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + + * channel, data=Line/Inst (2bytes) + * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes) + * ?? wValue=0x0803/04, ?? (2bytes) + * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes) + * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes) + * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte) + * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes) + * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes) + * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes) + * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes) + * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes) + * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff) + * + * USB reads: (i.e. actually issued by original software) + * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!) + * 0x29 wValue=0x0100 sample-rate(4bytes) + * wValue=0x0200 ?? 1byte (only once) + * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ?? + * + * USB reads with bRequest = 0x03 = UAC2_CS_MEM + * 0x3c wValue=0x0002 1byte: sync status (locked=1) + * wValue=0x0000 18*2byte: peak meter (inputs) + * wValue=0x0001 8(?)*2byte: peak meter (mix) + * wValue=0x0003 6*2byte: peak meter (pcm/daw) + * + * USB write with bRequest = 0x03 + * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5 + * + * + * <ditaa> + * /--------------\ 18chn 6chn /--------------\ + * | Hardware in +--+-------\ /------+--+ ALSA PCM out | + * --------------/ | | | | --------------/ + * | | | | + * | v v | + * | +---------------+ | + * | \ Matrix Mux / | + * | +-----+-----+ | + * | | | + * | | 18chn | + * | v | + * | +-----------+ | + * | | Mixer | | + * | | Matrix | | + * | | | | + * | | 18x6 Gain | | + * | | stages | | + * | +-----+-----+ | + * | | | + * | | | + * | 18chn | 6chn | 6chn + * v v v + * ========================= + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +-----+-----+ +-----+-----+ + * | | + * | 6chn | + * v | + * +-------------+ | + * | Master Gain | | + * +------+------+ | + * | | + * | 6chn | 18chn + * | (3 stereo pairs) | + * /--------------\ | | /--------------\ + * | Hardware out |<--/ -->| ALSA PCM in | + * --------------/ --------------/ + * </ditaa> + * + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "power.h" + +#include "mixer_scarlett.h" + +/* some gui mixers can't handle negative ctl values */ +#define SND_SCARLETT_LEVEL_BIAS 128 +#define SND_SCARLETT_MATRIX_IN_MAX 18 +#define SND_SCARLETT_CONTROLS_MAX 10 + +enum { + SCARLETT_OUTPUTS, + SCARLETT_SWITCH_IMPEDANCE, + SCARLETT_SWITCH_PAD, +}; + +struct scarlett_mixer_elem_enum_info { + int start; + int len; + char **names; +}; + +struct scarlett_mixer_control { + unsigned char num; + unsigned char type; + const char *name; +}; + +struct scarlett_device_info { + int matrix_in; + int matrix_out; + int input_len; + int output_len; + + int pcm_start; + int analog_start; + int spdif_start; + int adat_start; + int mix_start; + + struct scarlett_mixer_elem_enum_info opt_master; + struct scarlett_mixer_elem_enum_info opt_matrix; + + int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX]; + + int num_controls; /* number of items in controls */ + const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX]; +}; + +int scarlett_init = 0; + +/********************** Enum Strings *************************/ + +static const struct scarlett_mixer_elem_enum_info opt_pad = { + .start = 0, + .len = 2, + .names = (char *[]){ + "0dB", "-10dB" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_impedance = { + .start = 0, + .len = 2, + .names = (char *[]){ + "Line", "Hi-Z" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_clock = { + .start = 1, + .len = 3, + .names = (char *[]){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_sync = { + .start = 0, + .len = 2, + .names = (char *[]){ + "No Lock", "Locked" + } +}; + +static int scarlett_ctl_switch_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_BOOLEAN; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = !val; /* invert mute logic for mixer */ + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_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 = (int)kctl->private_value + + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + + SND_SCARLETT_LEVEL_BIAS; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - + SND_SCARLETT_LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + + return snd_ctl_enum_info(uinfo, elem->channels, opt->len, + (const char * const *)opt->names); +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &val); + if (err < 0) + return err; + + if ((opt->start == -1) && (val > opt->len)) /* >= 0x20 */ + val = 0; + else + val = clamp(val - opt->start, 0, opt->len-1); + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int changed = 0; + int err, oval, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + opt->start; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, 0, 0, val); + if (err < 0) + return err; + + changed = 1; + } + + return changed; +} + +static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->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->id << 8); + int err; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, elem->channels); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + return 0; +} + +static struct snd_kcontrol_new usb_scarlett_ctl_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_switch_info, + .get = scarlett_ctl_switch_get, + .put = scarlett_ctl_switch_put, +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); + +static struct snd_kcontrol_new usb_scarlett_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_master = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_sync = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_meter_get, +}; + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int offset, int num, + int val_type, int channels, const char *name, + const struct scarlett_mixer_elem_enum_info *opt, + struct usb_mixer_elem_info **elem_ret +) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->mixer = mixer; + elem->control = offset; + elem->idx_off = num; + elem->id = index; + elem->val_type = val_type; + + elem->channels = channels; + + /* add scarlett_mixer_elem_enum_info struct */ + elem->private_data = (void *)opt; + + kctl = snd_ctl_new1(ncontrol, elem); + if (!kctl) { + kfree(elem); + return -ENOMEM; + } + kctl->private_free = snd_usb_mixer_elem_free; + + snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name); + + err = snd_ctl_add(mixer->chip->card, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +static int add_output_ctls(struct usb_mixer_interface *mixer, + int index, const char *name, + const struct scarlett_device_info *info) +{ + int err; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct usb_mixer_elem_info *elem; + + /* Add mute switch */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add volume control and initialize to 0 */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index, + USB_MIXER_S16, 1, mx, &info->opt_master, &elem); + if (err < 0) + return err; + + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index+1, + USB_MIXER_S16, 1, mx, &info->opt_master, &elem); + if (err < 0) + return err; + + return 0; +} + +/********************** device-specific config *************************/ + +/* untested... */ +static struct scarlett_device_info s6i6_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 6, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 27, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = NULL + }, + + .num_controls = 0, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +/* untested... */ +static struct scarlett_device_info s8i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 8, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 25, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = NULL + }, + + .num_controls = 7, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +static struct scarlett_device_info s18i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 18, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 6, + .spdif_start = 14, + .adat_start = 16, + .mix_start = 24, + + .opt_master = { + .start = -1, + .len = 31, + .names = NULL, + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .names = NULL, + }, + + .num_controls = 5, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + }, + + .matrix_mux_init = { + 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */ + 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */ + 14, 15, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static struct scarlett_device_info s18i8_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 8, + + .pcm_start = 0, + .analog_start = 8, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 26, + + .opt_master = { + .start = -1, + .len = 35, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */ + 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */ + 16, 17, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static struct scarlett_device_info s18i20_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 20, + + .pcm_start = 0, + .analog_start = 20, + .spdif_start = 28, + .adat_start = 30, + .mix_start = 38, + + .opt_master = { + .start = -1, + .len = 47, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" }, + { .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" }, + { .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" }, + { .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" }, + { .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" }, + { .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" }, + /*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/ + }, + + .matrix_mux_init = { + 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */ + 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */ + 28, 29, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + + +static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, + struct scarlett_device_info *info) +{ + int i = 0; + int err = 0; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const struct scarlett_mixer_control *ctl; + struct usb_mixer_elem_info *elem; + + /* create master switch and playback volume */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, 0, + USB_MIXER_S16, 1, "Master Playback Switch", NULL, + &elem); + if (err < 0) + return err; + + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, 0, + USB_MIXER_S16, 1, "Master Playback Volume", NULL, + &elem); + if (err < 0) + return err; + + /* iterate through controls in info struct and create each one */ + for (i = 0; i < info->num_controls; i++) { + ctl = &info->controls[i]; + + switch (ctl->type) { + case SCARLETT_OUTPUTS: + err = add_output_ctls(mixer, ctl->num, ctl->name, info); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_IMPEDANCE: + sprintf(mx, "Input %d Impedance Switch", + ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x09, ctl->num, USB_MIXER_S16, 1, mx, + &opt_impedance, &elem); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_PAD: + sprintf(mx, "Input %d Pad Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x0b, ctl->num, USB_MIXER_S16, 1, mx, + &opt_pad, &elem); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* + * Create enumeration strings from device_info + */ +int scarlett_create_strings_from_info(struct scarlett_device_info *info) +{ + int i = 0; + char **names; + + names = kmalloc_array(info->opt_master.len, sizeof(char *), GFP_KERNEL); + if (!names) { + kfree(names); + return -ENOMEM; + } + + for (i = 0; i < info->opt_master.len; i++) { + names[i] = kmalloc_array(SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + sizeof(char), GFP_KERNEL); + if (!names[i]) { + kfree(names); + return -ENOMEM; + } + if (i > info->mix_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Mix %c", 'A'+(i - info->mix_start - 1)); + } else if (i > info->adat_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "ADAT %d", i - info->adat_start); + } else if (i > info->spdif_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "SPDIF %d", i - info->spdif_start); + } else if (i > info->analog_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Analog %d", i - info->analog_start); + } else if (i > info->pcm_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "PCM %d", i - info->pcm_start); + } else { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Off"); + } + } + + /* assign to the appropriate control */ + info->opt_master.names = names; + info->opt_matrix.names = names; + + return 0; +} + +/* + * Create and initialize a mixer for the Focusrite(R) Scarlett + */ +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct scarlett_device_info *info; + struct usb_mixer_elem_info *elem; + static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8012): + info = &s6i6_info; + break; + case USB_ID(0x1235, 0x8002): + info = &s8i6_info; + break; + case USB_ID(0x1235, 0x8004): + info = &s18i6_info; + break; + case USB_ID(0x1235, 0x8014): + info = &s18i8_info; + break; + case USB_ID(0x1235, 0x800c): + info = &s18i20_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + /* generate strings dynamically for each control */ + err = scarlett_create_strings_from_info(info); + if (err < 0) + return err; + + /* generic function to create controls */ + err = scarlett_controls_create_generic(mixer, info); + if (err < 0) + return err; + + /* setup matrix controls */ + for (i = 0; i < info->matrix_in; i++) { + snprintf(mx, sizeof(mx), "Matrix %02d Input Playback Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x32, 0x06, i, + USB_MIXER_S16, 1, mx, &info->opt_matrix, + &elem); + if (err < 0) + return err; + + for (o = 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); + err = add_new_ctl(mixer, &usb_scarlett_ctl, 0x3c, 0x00, + (i << 3) + (o & 0x07), USB_MIXER_S16, + 1, mx, NULL, &elem); + if (err < 0) + return err; + + } + } + + for (i = 0; i < info->input_len; i++) { + snprintf(mx, sizeof(mx), "Input Source %02d Capture Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x34, 0x00, i, + USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + } + + /* val_len == 1 needed here */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0, + USB_MIXER_U8, 1, "Sample Clock Source", + &opt_clock, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + 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) | + (0x29 << 8), sample_rate_buffer, 4); + if (err < 0) + return err; + + return err; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H + +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_H */
At Thu, 6 Nov 2014 08:33:38 -0600, Chris J Arges wrote:
--- /dev/null +++ b/sound/usb/mixer_scarlett.c
....
+int scarlett_init = 0;
What's this?
+/********************** Enum Strings *************************/
+static const struct scarlett_mixer_elem_enum_info opt_pad = {
- .start = 0,
- .len = 2,
- .names = (char *[]){
Better to keep names field const to avoid the cast. It might be needed to cast for the dynamic arrays, but then you need the cast only once there, instead of spreading cast over all codes.
+static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
- int err, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0, &val);
- if (err < 0)
return err;
- if ((opt->start == -1) && (val > opt->len)) /* >= 0x20 */
val = 0;
- else
val = clamp(val - opt->start, 0, opt->len-1);
Please give a bit more explanation here. This code relies on the fact that the hardware returns a high value for non-selection.
- ucontrol->value.enumerated.item[0] = val;
- return 0;
+}
+static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct scarlett_mixer_elem_enum_info *opt = elem->private_data;
- int changed = 0;
- int err, oval, val;
- err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval);
- if (err < 0)
return err;
- val = ucontrol->value.integer.value[0];
- val = val + opt->start;
Ditto here. It assumes that the write of an invalid value would result in the non-selection.
+static int add_new_ctl(struct usb_mixer_interface *mixer,
const struct snd_kcontrol_new *ncontrol,
int index, int offset, int num,
int val_type, int channels, const char *name,
const struct scarlett_mixer_elem_enum_info *opt,
struct usb_mixer_elem_info **elem_ret
+) +{
...
- snprintf(kctl->id.name, sizeof(kctl->id.name), "%s", name);
strlcpy() is more straightforward.
+int scarlett_create_strings_from_info(struct scarlett_device_info *info) +{
- int i = 0;
- char **names;
- names = kmalloc_array(info->opt_master.len, sizeof(char *), GFP_KERNEL);
- if (!names) {
kfree(names);
return -ENOMEM;
- }
- for (i = 0; i < info->opt_master.len; i++) {
names[i] = kmalloc_array(SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
sizeof(char), GFP_KERNEL);
if (!names[i]) {
kfree(names);
return -ENOMEM;
Memory leaks. names[0..i-1] have to be freed, too.
thanks,
Takashi
This is v5 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
[v3]
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
[v4]
This version removes the per-mixer control creation functions and uses a generic function based on structure data. Macros used for control addition are removed and the plain function is used instead. Hardcoded text block is removed and macros to define strings are used instead. Hardcoded control initialization has been removed.
[v5]
In this version, HW saving functionality has been removed in this initial patchset. Macros for function calls are removed for readability. Strings for enums are created dynamically using the info structures. String lengths for controls are now all SNDRV_CTL_ELEM_ID_NAME_MAXLEN in length.
[v6]
Removed dead variables. Made names const char * const * again. Adjusted scarlett_ctl_enum_* functions to be a bit clearer. Used strlcpy where appropriate. Properly free two dimensional array.
Chris J Arges (4): Revert "ALSA: usb-audio: Add quirk for Focusrite Scarlett ALSA: usb-audio: Add private_data pointer to usb_mixer_elem_info ALSA: usb-audio: make set_*_mix_values functions public ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
sound/usb/Makefile | 1 + sound/usb/mixer.c | 34 +- sound/usb/mixer.h | 9 + sound/usb/mixer_quirks.c | 18 +- sound/usb/mixer_scarlett.c | 1001 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + sound/usb/quirks-table.h | 51 --- 7 files changed, 1044 insertions(+), 76 deletions(-) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
This reverts commit 1762a59d8e8b5e99f6f4a0f292b40f3cacb108ba.
This quirk is not needed because support for the Scarlett mixers will be added.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/quirks-table.h | 51 ------------------------------------------------ 1 file changed, 51 deletions(-)
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index c657752..51686c7 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2667,57 +2667,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), .type = QUIRK_MIDI_NOVATION } }, -{ - /* - * Focusrite Scarlett 18i6 - * - * Avoid mixer creation, which otherwise fails because some of - * the interface descriptor subtypes for interface 0 are - * unknown. That should be fixed or worked-around but this at - * least allows the device to be used successfully with a DAW - * and an external mixer. See comments below about other - * ignored interfaces. - */ - USB_DEVICE(0x1235, 0x8004), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Focusrite", - .product_name = "Scarlett 18i6", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Device Firmware Update) */ - .ifnum = 5, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } - } - } -},
/* Access Music devices */ {
Add a private_data pointer to usb_mixer_elem_info to allow other mixer implementations to extend the structure as necessary.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 73b1f64..8df9a73 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -53,6 +53,7 @@ struct usb_mixer_elem_info { int cached; int cache_val[MAX_CHANNELS]; u8 initialized; + void *private_data; };
int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
Make the functions set_cur_mix_value and get_cur_mix_value accessible by files that include mixer.h. In addition make usb_mixer_elem_free accessible. This allows reuse of these functions by mixers that may require quirks.
The following summarizes the renamed functions: - set_cur_mix_value -> snd_usb_set_cur_mix_value - get_cur_mix_value -> snd_usb_get_cur_mix_value - usb_mixer_elem_free -> snd_usb_mixer_elem_free
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.c | 34 +++++++++++++++++----------------- sound/usb/mixer.h | 8 ++++++++ sound/usb/mixer_quirks.c | 9 +-------- 3 files changed, 26 insertions(+), 25 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 2e4a9db..6b169fb 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -412,7 +412,7 @@ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, value); }
-static int get_cur_mix_value(struct usb_mixer_elem_info *cval, +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int *value) { int err; @@ -497,7 +497,7 @@ static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); }
-static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, +int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int value) { int err; @@ -815,7 +815,7 @@ static struct usb_feature_control_info audio_feature_info[] = { };
/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) +void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl) { kfree(kctl->private_data); kctl->private_data = NULL; @@ -998,7 +998,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, else test -= cval->res; if (test < cval->min || test > cval->max || - set_cur_mix_value(cval, minchn, 0, test) || + snd_usb_set_cur_mix_value(cval, minchn, 0, test) || get_cur_mix_raw(cval, minchn, &check)) { cval->res = last_valid_res; break; @@ -1007,7 +1007,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; cval->res *= 2; } - set_cur_mix_value(cval, minchn, 0, saved); + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); }
cval->initialized = 1; @@ -1086,7 +1086,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &val); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1096,7 +1096,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, return 0; } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &val); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1118,26 +1118,26 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &oval); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[cnt]; val = get_abs_value(cval, val); if (oval != val) { - set_cur_mix_value(cval, c + 1, cnt, val); + snd_usb_set_cur_mix_value(cval, c + 1, cnt, val); changed = 1; } cnt++; } } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &oval); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[0]; val = get_abs_value(cval, val); if (val != oval) { - set_cur_mix_value(cval, 0, 0, val); + snd_usb_set_cur_mix_value(cval, 0, 0, val); changed = 1; } } @@ -1250,7 +1250,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
/* * If all channels in the mask are marked read-only, make the control - * read-only. set_cur_mix_value() will check the mask again and won't + * read-only. snd_usb_set_cur_mix_value() will check the mask again and won't * issue write commands to read-only channels. */ if (cval->channels == readonly_mask) @@ -1263,7 +1263,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; @@ -1547,7 +1547,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (!len) @@ -1847,7 +1847,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, kfree(cval); return -ENOMEM; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) { /* nothing */ ; @@ -2530,7 +2530,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) if (!(cval->cmask & (1 << c))) continue; if (cval->cached & (1 << c)) { - err = set_cur_mix_value(cval, c + 1, idx, + err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) return err; @@ -2540,7 +2540,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) } else { /* master */ if (cval->cached) { - err = set_cur_mix_value(cval, 0, 0, *cval->cache_val); + err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); if (err < 0) return err; } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 8df9a73..8db2980 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -76,4 +76,12 @@ 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_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, + int index, int value); + +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, + int channel, int index, int *value); + +extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl); + #endif /* __USBMIXER_H */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index f119a41..c9665bf 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -52,13 +52,6 @@ struct std_mono_table { snd_kcontrol_tlv_rw_t *tlv_callback; };
-/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) -{ - kfree(kctl->private_data); - kctl->private_data = NULL; -} - /* This function allows for the creation of standard UAC controls. * See the quirks for M-Audio FTUs or Ebox-44. * If you don't want to set a TLV callback pass NULL. @@ -108,7 +101,7 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer,
/* Set name */ snprintf(kctl->id.name, sizeof(kctl->id.name), name); - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
/* set TLV */ if (tlv_callback) {
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. The custom scarlett_mixer_elem_info struct was replaced with the more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.
This patch also makes additional modifications based on upstream comments. Individual control creation functions are removed and a generic function is no used. Macros for function calls are removed to improve readability. Hardcoded control initialization is removed. Save to HW functionality has been removed. Strings for enums are created dynamically for the mixer. Strings used for controls are now SNDRV_CTL_ELEM_ID_NAME_MAXLEN length.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/Makefile | 1 + sound/usb/mixer_quirks.c | 9 + sound/usb/mixer_scarlett.c | 1001 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + 4 files changed, 1017 insertions(+) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..bcee406 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -9,6 +9,7 @@ snd-usb-audio-objs := card.o \ helper.o \ mixer.o \ mixer_quirks.o \ + mixer_scarlett.o \ pcm.o \ proc.o \ quirks.o \ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index c9665bf..d06d27a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -41,6 +41,7 @@ #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" +#include "mixer_scarlett.h" #include "helper.h"
extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; @@ -1664,6 +1665,14 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) /* detection is disabled in mixer_maps.c */ err = snd_create_std_mono_table(mixer, ebox44_table); break; + + case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */ + case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */ + case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */ + case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */ + case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */ + err = snd_scarlett_controls_create(mixer); + break; }
return err; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c new file mode 100644 index 0000000..a1bb0f6 --- /dev/null +++ b/sound/usb/mixer_scarlett.c @@ -0,0 +1,1001 @@ +/* + * Scarlett 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Rewritten and extended to support more models, e.g. Scarlett 18i8. + * + * Many features of Scarlett 18i6 do not lend themselves to be implemented + * as simple mixer-quirk -- or at least I don't see a way how to do that, yet. + * Hence the top parts of this file is a 1:1 copy of select static functions + * from mixer.c to implement the interface. + * Suggestions how to avoid this code duplication are very welcome. + * + * eventually this should either be integrated as quirk into mixer_quirks.c + * or become a standalone module. + * + * This source hardcodes the URBs for the Scarlett, + * 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. + * + * NB. Neither the OSX nor the win driver provided by Focusrite performs + * discovery, they seem to operate the same as this driver. + */ + +/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface. + * + * The protocol was reverse engineered by looking at communication between + * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 + * (firmware v305) using wireshark and usbmon in January 2013. + * Extended in July 2013. + * + * this mixer gives complete access to all features of the device: + * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z) + * - select clock source + * - dynamic input to mixer-matrix assignment + * - 18 x 6 mixer-matrix gain stages + * - bus routing & volume control + * - save setting to hardware + * - automatic re-initialization on connect if device was power-cycled + * - peak monitoring of all 3 buses (18 input, 6 DAW input, 6 route channels) + * (changing the samplerate and buffersize is supported by the PCM interface) + * + * + * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR) + * wIndex + * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + + * channel, data=Line/Inst (2bytes) + * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes) + * ?? wValue=0x0803/04, ?? (2bytes) + * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes) + * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes) + * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte) + * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes) + * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes) + * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes) + * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes) + * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes) + * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff) + * + * USB reads: (i.e. actually issued by original software) + * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!) + * 0x29 wValue=0x0100 sample-rate(4bytes) + * wValue=0x0200 ?? 1byte (only once) + * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ?? + * + * USB reads with bRequest = 0x03 = UAC2_CS_MEM + * 0x3c wValue=0x0002 1byte: sync status (locked=1) + * wValue=0x0000 18*2byte: peak meter (inputs) + * wValue=0x0001 8(?)*2byte: peak meter (mix) + * wValue=0x0003 6*2byte: peak meter (pcm/daw) + * + * USB write with bRequest = 0x03 + * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5 + * + * + * <ditaa> + * /--------------\ 18chn 6chn /--------------\ + * | Hardware in +--+-------\ /------+--+ ALSA PCM out | + * --------------/ | | | | --------------/ + * | | | | + * | v v | + * | +---------------+ | + * | \ Matrix Mux / | + * | +-----+-----+ | + * | | | + * | | 18chn | + * | v | + * | +-----------+ | + * | | Mixer | | + * | | Matrix | | + * | | | | + * | | 18x6 Gain | | + * | | stages | | + * | +-----+-----+ | + * | | | + * | | | + * | 18chn | 6chn | 6chn + * v v v + * ========================= + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +-----+-----+ +-----+-----+ + * | | + * | 6chn | + * v | + * +-------------+ | + * | Master Gain | | + * +------+------+ | + * | | + * | 6chn | 18chn + * | (3 stereo pairs) | + * /--------------\ | | /--------------\ + * | Hardware out |<--/ -->| ALSA PCM in | + * --------------/ --------------/ + * </ditaa> + * + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "power.h" + +#include "mixer_scarlett.h" + +/* some gui mixers can't handle negative ctl values */ +#define SND_SCARLETT_LEVEL_BIAS 128 +#define SND_SCARLETT_MATRIX_IN_MAX 18 +#define SND_SCARLETT_CONTROLS_MAX 10 + +enum { + SCARLETT_OUTPUTS, + SCARLETT_SWITCH_IMPEDANCE, + SCARLETT_SWITCH_PAD, +}; + +struct scarlett_mixer_elem_enum_info { + int start; + int len; + char const * const *names; +}; + +struct scarlett_mixer_control { + unsigned char num; + unsigned char type; + const char *name; +}; + +struct scarlett_device_info { + int matrix_in; + int matrix_out; + int input_len; + int output_len; + + int pcm_start; + int analog_start; + int spdif_start; + int adat_start; + int mix_start; + + struct scarlett_mixer_elem_enum_info opt_master; + struct scarlett_mixer_elem_enum_info opt_matrix; + + int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX]; + + int num_controls; /* number of items in controls */ + const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX]; +}; + +/********************** Enum Strings *************************/ + +static const struct scarlett_mixer_elem_enum_info opt_pad = { + .start = 0, + .len = 2, + .names = (char const * const []){ + "0dB", "-10dB" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_impedance = { + .start = 0, + .len = 2, + .names = (char const * const []){ + "Line", "Hi-Z" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_clock = { + .start = 1, + .len = 3, + .names = (char const * const []){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_sync = { + .start = 0, + .len = 2, + .names = (char const * const []){ + "No Lock", "Locked" + } +}; + +static int scarlett_ctl_switch_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_BOOLEAN; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = !val; /* invert mute logic for mixer */ + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_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 = (int)kctl->private_value + + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + + SND_SCARLETT_LEVEL_BIAS; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - + SND_SCARLETT_LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + + return snd_ctl_enum_info(uinfo, elem->channels, opt->len, + (const char * const *)opt->names); +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &val); + if (err < 0) + return err; + + val = clamp(val - opt->start, 0, opt->len-1); + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, oval, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + opt->start; + if (val != oval) { + snd_usb_set_cur_mix_value(elem, 0, 0, val); + return 1; + } + return 0; +} + +static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->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->id << 8); + int err; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, elem->channels); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + return 0; +} + +static struct snd_kcontrol_new usb_scarlett_ctl_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_switch_info, + .get = scarlett_ctl_switch_get, + .put = scarlett_ctl_switch_put, +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); + +static struct snd_kcontrol_new usb_scarlett_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_master = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_sync = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_meter_get, +}; + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int offset, int num, + int val_type, int channels, const char *name, + const struct scarlett_mixer_elem_enum_info *opt, + struct usb_mixer_elem_info **elem_ret +) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->mixer = mixer; + elem->control = offset; + elem->idx_off = num; + elem->id = index; + elem->val_type = val_type; + + elem->channels = channels; + + /* add scarlett_mixer_elem_enum_info struct */ + elem->private_data = (void *)opt; + + 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_ctl_add(mixer->chip->card, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +static int add_output_ctls(struct usb_mixer_interface *mixer, + int index, const char *name, + const struct scarlett_device_info *info) +{ + int err; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct usb_mixer_elem_info *elem; + + /* Add mute switch */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add volume control and initialize to 0 */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index, + USB_MIXER_S16, 1, mx, &info->opt_master, &elem); + if (err < 0) + return err; + + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x33, 0x00, 2*index+1, + USB_MIXER_S16, 1, mx, &info->opt_master, &elem); + if (err < 0) + return err; + + return 0; +} + +/********************** device-specific config *************************/ + +/* untested... */ +static struct scarlett_device_info s6i6_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 6, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 27, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = NULL + }, + + .num_controls = 0, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +/* untested... */ +static struct scarlett_device_info s8i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 8, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 12, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 18, + + .opt_master = { + .start = -1, + .len = 25, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .names = NULL + }, + + .num_controls = 7, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +static struct scarlett_device_info s18i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 18, + .output_len = 6, + + .pcm_start = 0, + .analog_start = 6, + .spdif_start = 14, + .adat_start = 16, + .mix_start = 24, + + .opt_master = { + .start = -1, + .len = 31, + .names = NULL, + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .names = NULL, + }, + + .num_controls = 5, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + }, + + .matrix_mux_init = { + 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */ + 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */ + 14, 15, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static struct scarlett_device_info s18i8_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 8, + + .pcm_start = 0, + .analog_start = 8, + .spdif_start = 16, + .adat_start = 18, + .mix_start = 26, + + .opt_master = { + .start = -1, + .len = 35, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */ + 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */ + 16, 17, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static struct scarlett_device_info s18i20_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 20, + + .pcm_start = 0, + .analog_start = 20, + .spdif_start = 28, + .adat_start = 30, + .mix_start = 38, + + .opt_master = { + .start = -1, + .len = 47, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" }, + { .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" }, + { .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" }, + { .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" }, + { .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" }, + { .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" }, + /*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/ + }, + + .matrix_mux_init = { + 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */ + 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */ + 28, 29, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + + +static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, + struct scarlett_device_info *info) +{ + int i = 0; + int err = 0; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const struct scarlett_mixer_control *ctl; + struct usb_mixer_elem_info *elem; + + /* create master switch and playback volume */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, 0, + USB_MIXER_S16, 1, "Master Playback Switch", NULL, + &elem); + if (err < 0) + return err; + + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, 0, + USB_MIXER_S16, 1, "Master Playback Volume", NULL, + &elem); + if (err < 0) + return err; + + /* iterate through controls in info struct and create each one */ + for (i = 0; i < info->num_controls; i++) { + ctl = &info->controls[i]; + + switch (ctl->type) { + case SCARLETT_OUTPUTS: + err = add_output_ctls(mixer, ctl->num, ctl->name, info); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_IMPEDANCE: + sprintf(mx, "Input %d Impedance Switch", + ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x09, ctl->num, USB_MIXER_S16, 1, mx, + &opt_impedance, &elem); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_PAD: + sprintf(mx, "Input %d Pad Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x0b, ctl->num, USB_MIXER_S16, 1, mx, + &opt_pad, &elem); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* + * Create enumeration strings from device_info + */ +int scarlett_create_strings_from_info(struct scarlett_device_info *info) +{ + int i = 0, j = 0; + char **names; + + names = kmalloc_array(info->opt_master.len, sizeof(char *), GFP_KERNEL); + if (!names) { + kfree(names); + return -ENOMEM; + } + + for (i = 0; i < info->opt_master.len; i++) { + names[i] = kmalloc_array(SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + sizeof(char), GFP_KERNEL); + if (!names[i]) { + for (j = 0; j < i-1; j++) + kfree(names[j]); + kfree(names); + return -ENOMEM; + } + if (i > info->mix_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Mix %c", 'A'+(i - info->mix_start - 1)); + } else if (i > info->adat_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "ADAT %d", i - info->adat_start); + } else if (i > info->spdif_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "SPDIF %d", i - info->spdif_start); + } else if (i > info->analog_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Analog %d", i - info->analog_start); + } else if (i > info->pcm_start) { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "PCM %d", i - info->pcm_start); + } else { + snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN, + "Off"); + } + } + + /* assign to the appropriate control */ + info->opt_master.names = (const char * const *)names; + info->opt_matrix.names = (const char * const *)names; + + return 0; +} + +/* + * Create and initialize a mixer for the Focusrite(R) Scarlett + */ +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct scarlett_device_info *info; + struct usb_mixer_elem_info *elem; + static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8012): + info = &s6i6_info; + break; + case USB_ID(0x1235, 0x8002): + info = &s8i6_info; + break; + case USB_ID(0x1235, 0x8004): + info = &s18i6_info; + break; + case USB_ID(0x1235, 0x8014): + info = &s18i8_info; + break; + case USB_ID(0x1235, 0x800c): + info = &s18i20_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + /* generate strings dynamically for each control */ + err = scarlett_create_strings_from_info(info); + if (err < 0) + return err; + + /* generic function to create controls */ + err = scarlett_controls_create_generic(mixer, info); + if (err < 0) + return err; + + /* setup matrix controls */ + for (i = 0; i < info->matrix_in; i++) { + snprintf(mx, sizeof(mx), "Matrix %02d Input Playback Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x32, 0x06, i, + USB_MIXER_S16, 1, mx, &info->opt_matrix, + &elem); + if (err < 0) + return err; + + for (o = 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); + err = add_new_ctl(mixer, &usb_scarlett_ctl, 0x3c, 0x00, + (i << 3) + (o & 0x07), USB_MIXER_S16, + 1, mx, NULL, &elem); + if (err < 0) + return err; + + } + } + + for (i = 0; i < info->input_len; i++) { + snprintf(mx, sizeof(mx), "Input Source %02d Capture Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x34, 0x00, i, + USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + } + + /* val_len == 1 needed here */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0, + USB_MIXER_U8, 1, "Sample Clock Source", + &opt_clock, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + 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) | + (0x29 << 8), sample_rate_buffer, 4); + if (err < 0) + return err; + + return err; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H + +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_H */
At Mon, 10 Nov 2014 12:59:18 -0600, Chris J Arges wrote:
+/*
- Create enumeration strings from device_info
- */
+int scarlett_create_strings_from_info(struct scarlett_device_info *info) +{
- int i = 0, j = 0;
Superfluous initializations.
- char **names;
- names = kmalloc_array(info->opt_master.len, sizeof(char *), GFP_KERNEL);
- if (!names) {
kfree(names);
Superfluous kfree().
return -ENOMEM;
- }
- for (i = 0; i < info->opt_master.len; i++) {
names[i] = kmalloc_array(SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
sizeof(char), GFP_KERNEL);
Just use kmalloc().
if (!names[i]) {
for (j = 0; j < i-1; j++)
kfree(names[j]);
kfree(names);
return -ENOMEM;
}
if (i > info->mix_start) {
snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
"Mix %c", 'A'+(i - info->mix_start - 1));
} else if (i > info->adat_start) {
snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
"ADAT %d", i - info->adat_start);
} else if (i > info->spdif_start) {
snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
"SPDIF %d", i - info->spdif_start);
} else if (i > info->analog_start) {
snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
"Analog %d", i - info->analog_start);
} else if (i > info->pcm_start) {
snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
"PCM %d", i - info->pcm_start);
} else {
snprintf(names[i], SNDRV_CTL_ELEM_ID_NAME_MAXLEN,
"Off");
}
BTW, another way would be to use kasprintf() and directly assign to names[i]. It'll save memory usage a bit. Although this won't guarantee the max length, but these strings are obviously short enough, so no big worry about it.
But, it's really a small difference, so I don't mind either way.
However...
- }
- /* assign to the appropriate control */
- info->opt_master.names = (const char * const *)names;
- info->opt_matrix.names = (const char * const *)names;
... I guess these will be leaked without destructor? This has to be fixed. You need to add a flag indicating the need of kfree() and do it in the own destructor accordingly.
thanks,
Takashi
<snip>
However...
- }
- /* assign to the appropriate control */
- info->opt_master.names = (const char * const *)names;
- info->opt_matrix.names = (const char * const *)names;
... I guess these will be leaked without destructor? This has to be fixed. You need to add a flag indicating the need of kfree() and do it in the own destructor accordingly.
thanks,
Takashi
Where is the best place for this destructor?
At the end of the snd_scarlett_controls_create function doesn't work due to opt_*.names being referenced by scarlett_ctl_enum_info afterwards.
Creating a custom private_free callback doesn't seem correct either as I should only need to free the memory once and not repeatedly. However with a flag I could check and only free once on the callback.
Thanks, --chris
At Mon, 10 Nov 2014 16:00:17 -0600, Chris J Arges wrote:
<snip> > However... > >> + } >> + >> + /* assign to the appropriate control */ >> + info->opt_master.names = (const char * const *)names; >> + info->opt_matrix.names = (const char * const *)names; > > ... I guess these will be leaked without destructor? This has to be > fixed. You need to add a flag indicating the need of kfree() and do > it in the own destructor accordingly. > > > thanks, > > Takashi > Where is the best place for this destructor?
At the end of the snd_scarlett_controls_create function doesn't work due to opt_*.names being referenced by scarlett_ctl_enum_info afterwards.
Creating a custom private_free callback doesn't seem correct either as I should only need to free the memory once and not repeatedly. However with a flag I could check and only free once on the callback.
Well, further looking at the code, it seems more buggy than I thought. scarlett_create_strings_from_info() may override the existing pointer that has been allocated. Again this leaks.
So, in the end, the cleanest way would be to generate a string in the info callback itself. It doesn't need to allocate at all, it just writes to uinfo->value.enumerated.name dynamically.
Takashi
This is v5 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
[v3]
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
[v4]
This version removes the per-mixer control creation functions and uses a generic function based on structure data. Macros used for control addition are removed and the plain function is used instead. Hardcoded text block is removed and macros to define strings are used instead. Hardcoded control initialization has been removed.
[v5]
In this version, HW saving functionality has been removed in this initial patchset. Macros for function calls are removed for readability. Strings for enums are created dynamically using the info structures. String lengths for controls are now all SNDRV_CTL_ELEM_ID_NAME_MAXLEN in length.
[v6]
Removed dead variables. Made names const char * const * again. Adjusted scarlett_ctl_enum_* functions to be a bit clearer. Used strlcpy where appropriate. Properly free two dimensional array.
[v7]
Generate strings directly in enum_info function instead of dynamically allocating into another array and copying. Remove unnecessary initializations, kfrees and use kmalloc instead of kmalloc_array. Update comments to reflect current mixer functionality.
Chris J Arges (4): Revert "ALSA: usb-audio: Add quirk for Focusrite Scarlett ALSA: usb-audio: Add private_data pointer to usb_mixer_elem_info ALSA: usb-audio: make set_*_mix_values functions public ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
sound/usb/Makefile | 1 + sound/usb/mixer.c | 34 +- sound/usb/mixer.h | 9 + sound/usb/mixer_quirks.c | 18 +- sound/usb/mixer_scarlett.c | 1001 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + sound/usb/quirks-table.h | 51 --- 7 files changed, 1044 insertions(+), 76 deletions(-) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
This reverts commit 1762a59d8e8b5e99f6f4a0f292b40f3cacb108ba.
This quirk is not needed because support for the Scarlett mixers will be added.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/quirks-table.h | 51 ------------------------------------------------ 1 file changed, 51 deletions(-)
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index c657752..51686c7 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2667,57 +2667,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), .type = QUIRK_MIDI_NOVATION } }, -{ - /* - * Focusrite Scarlett 18i6 - * - * Avoid mixer creation, which otherwise fails because some of - * the interface descriptor subtypes for interface 0 are - * unknown. That should be fixed or worked-around but this at - * least allows the device to be used successfully with a DAW - * and an external mixer. See comments below about other - * ignored interfaces. - */ - USB_DEVICE(0x1235, 0x8004), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Focusrite", - .product_name = "Scarlett 18i6", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = & (const struct snd_usb_audio_quirk[]) { - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 0, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - .ifnum = 2, - .type = QUIRK_AUDIO_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Control Device) */ - .ifnum = 3, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = 4, - .type = QUIRK_MIDI_STANDARD_INTERFACE - }, - { - /* InterfaceSubClass 1 (Device Firmware Update) */ - .ifnum = 5, - .type = QUIRK_IGNORE_INTERFACE - }, - { - .ifnum = -1 - } - } - } -},
/* Access Music devices */ {
Add a private_data pointer to usb_mixer_elem_info to allow other mixer implementations to extend the structure as necessary.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 73b1f64..8df9a73 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -53,6 +53,7 @@ struct usb_mixer_elem_info { int cached; int cache_val[MAX_CHANNELS]; u8 initialized; + void *private_data; };
int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
Make the functions set_cur_mix_value and get_cur_mix_value accessible by files that include mixer.h. In addition make usb_mixer_elem_free accessible. This allows reuse of these functions by mixers that may require quirks.
The following summarizes the renamed functions: - set_cur_mix_value -> snd_usb_set_cur_mix_value - get_cur_mix_value -> snd_usb_get_cur_mix_value - usb_mixer_elem_free -> snd_usb_mixer_elem_free
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/mixer.c | 34 +++++++++++++++++----------------- sound/usb/mixer.h | 8 ++++++++ sound/usb/mixer_quirks.c | 9 +-------- 3 files changed, 26 insertions(+), 25 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 2e4a9db..6b169fb 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -412,7 +412,7 @@ static inline int get_cur_mix_raw(struct usb_mixer_elem_info *cval, value); }
-static int get_cur_mix_value(struct usb_mixer_elem_info *cval, +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int *value) { int err; @@ -497,7 +497,7 @@ static int set_cur_ctl_value(struct usb_mixer_elem_info *cval, return snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, validx, value); }
-static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, +int snd_usb_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int value) { int err; @@ -815,7 +815,7 @@ static struct usb_feature_control_info audio_feature_info[] = { };
/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) +void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl) { kfree(kctl->private_data); kctl->private_data = NULL; @@ -998,7 +998,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, else test -= cval->res; if (test < cval->min || test > cval->max || - set_cur_mix_value(cval, minchn, 0, test) || + snd_usb_set_cur_mix_value(cval, minchn, 0, test) || get_cur_mix_raw(cval, minchn, &check)) { cval->res = last_valid_res; break; @@ -1007,7 +1007,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, break; cval->res *= 2; } - set_cur_mix_value(cval, minchn, 0, saved); + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); }
cval->initialized = 1; @@ -1086,7 +1086,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &val); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1096,7 +1096,7 @@ static int mixer_ctl_feature_get(struct snd_kcontrol *kcontrol, return 0; } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &val); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &val); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = get_relative_value(cval, val); @@ -1118,26 +1118,26 @@ static int mixer_ctl_feature_put(struct snd_kcontrol *kcontrol, for (c = 0; c < MAX_CHANNELS; c++) { if (!(cval->cmask & (1 << c))) continue; - err = get_cur_mix_value(cval, c + 1, cnt, &oval); + err = snd_usb_get_cur_mix_value(cval, c + 1, cnt, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[cnt]; val = get_abs_value(cval, val); if (oval != val) { - set_cur_mix_value(cval, c + 1, cnt, val); + snd_usb_set_cur_mix_value(cval, c + 1, cnt, val); changed = 1; } cnt++; } } else { /* master channel */ - err = get_cur_mix_value(cval, 0, 0, &oval); + err = snd_usb_get_cur_mix_value(cval, 0, 0, &oval); if (err < 0) return cval->mixer->ignore_ctl_error ? 0 : err; val = ucontrol->value.integer.value[0]; val = get_abs_value(cval, val); if (val != oval) { - set_cur_mix_value(cval, 0, 0, val); + snd_usb_set_cur_mix_value(cval, 0, 0, val); changed = 1; } } @@ -1250,7 +1250,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
/* * If all channels in the mask are marked read-only, make the control - * read-only. set_cur_mix_value() will check the mask again and won't + * read-only. snd_usb_set_cur_mix_value() will check the mask again and won't * issue write commands to read-only channels. */ if (cval->channels == readonly_mask) @@ -1263,7 +1263,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); mapped_name = len != 0; @@ -1547,7 +1547,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, kfree(cval); return; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name)); if (!len) @@ -1847,7 +1847,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, kfree(cval); return -ENOMEM; } - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) { /* nothing */ ; @@ -2530,7 +2530,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) if (!(cval->cmask & (1 << c))) continue; if (cval->cached & (1 << c)) { - err = set_cur_mix_value(cval, c + 1, idx, + err = snd_usb_set_cur_mix_value(cval, c + 1, idx, cval->cache_val[idx]); if (err < 0) return err; @@ -2540,7 +2540,7 @@ static int restore_mixer_value(struct usb_mixer_elem_info *cval) } else { /* master */ if (cval->cached) { - err = set_cur_mix_value(cval, 0, 0, *cval->cache_val); + err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val); if (err < 0) return err; } diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 8df9a73..8db2980 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -76,4 +76,12 @@ 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_set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, + int index, int value); + +int snd_usb_get_cur_mix_value(struct usb_mixer_elem_info *cval, + int channel, int index, int *value); + +extern void snd_usb_mixer_elem_free(struct snd_kcontrol *kctl); + #endif /* __USBMIXER_H */ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index f119a41..c9665bf 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -52,13 +52,6 @@ struct std_mono_table { snd_kcontrol_tlv_rw_t *tlv_callback; };
-/* private_free callback */ -static void usb_mixer_elem_free(struct snd_kcontrol *kctl) -{ - kfree(kctl->private_data); - kctl->private_data = NULL; -} - /* This function allows for the creation of standard UAC controls. * See the quirks for M-Audio FTUs or Ebox-44. * If you don't want to set a TLV callback pass NULL. @@ -108,7 +101,7 @@ static int snd_create_std_mono_ctl_offset(struct usb_mixer_interface *mixer,
/* Set name */ snprintf(kctl->id.name, sizeof(kctl->id.name), name); - kctl->private_free = usb_mixer_elem_free; + kctl->private_free = snd_usb_mixer_elem_free;
/* set TLV */ if (tlv_callback) {
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Changes from the original code include removing the metering code along with dead code and comments. Compiler warnings were fixed. The code to initialize the sampling rate was causing a crash this was fixed as discussed on the mailing list. Error, and info messages were convered to dev_err and dev_info interfaces. The custom scarlett_mixer_elem_info struct was replaced with the more generic usb_mixer_elem_info to be able to recycle more code from mixer.c.
This patch also makes additional modifications based on upstream comments. Individual control creation functions are removed and a generic function is no used. Macros for function calls are removed to improve readability. Hardcoded control initialization is removed. Save to HW functionality has been removed. Strings for enums are created dynamically for the mixer. Strings used for controls are now SNDRV_CTL_ELEM_ID_NAME_MAXLEN length.
Signed-off-by: Chris J Arges chris.j.arges@canonical.com --- sound/usb/Makefile | 1 + sound/usb/mixer_quirks.c | 9 + sound/usb/mixer_scarlett.c | 967 +++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + 4 files changed, 983 insertions(+) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 2b92f0d..bcee406 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -9,6 +9,7 @@ snd-usb-audio-objs := card.o \ helper.o \ mixer.o \ mixer_quirks.o \ + mixer_scarlett.o \ pcm.o \ proc.o \ quirks.o \ diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index c9665bf..d06d27a 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -41,6 +41,7 @@ #include "usbaudio.h" #include "mixer.h" #include "mixer_quirks.h" +#include "mixer_scarlett.h" #include "helper.h"
extern struct snd_kcontrol_new *snd_usb_feature_unit_ctl; @@ -1664,6 +1665,14 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) /* detection is disabled in mixer_maps.c */ err = snd_create_std_mono_table(mixer, ebox44_table); break; + + case USB_ID(0x1235, 0x8012): /* Focusrite Scarlett 6i6 */ + case USB_ID(0x1235, 0x8002): /* Focusrite Scarlett 8i6 */ + case USB_ID(0x1235, 0x8004): /* Focusrite Scarlett 18i6 */ + case USB_ID(0x1235, 0x8014): /* Focusrite Scarlett 18i8 */ + case USB_ID(0x1235, 0x800c): /* Focusrite Scarlett 18i20 */ + err = snd_scarlett_controls_create(mixer); + break; }
return err; diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c new file mode 100644 index 0000000..a0a8745 --- /dev/null +++ b/sound/usb/mixer_scarlett.c @@ -0,0 +1,967 @@ +/* + * Scarlett 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* + * Rewritten and extended to support more models, e.g. Scarlett 18i8. + * + * 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. + * + * NB. Neither the OSX nor the win driver provided by Focusrite performs + * discovery, they seem to operate the same as this driver. + */ + +/* Mixer Interface for the Focusrite Scarlett 18i6 audio interface. + * + * The protocol was reverse engineered by looking at communication between + * Scarlett MixControl (v 1.2.128.0) and the Focusrite(R) Scarlett 18i6 + * (firmware v305) using wireshark and usbmon in January 2013. + * Extended in July 2013. + * + * this mixer gives complete access to all features of the device: + * - change Impedance of inputs (Line-in, Mic / Instrument, Hi-Z) + * - select clock source + * - dynamic input to mixer-matrix assignment + * - 18 x 6 mixer-matrix gain stages + * - bus routing & volume control + * - automatic re-initialization on connect if device was power-cycled + * + * USB URB commands overview (bRequest = 0x01 = UAC2_CS_CUR) + * wIndex + * 0x01 Analog Input line/instrument impedance switch, wValue=0x0901 + + * channel, data=Line/Inst (2bytes) + * pad (-10dB) switch, wValue=0x0b01 + channel, data=Off/On (2bytes) + * ?? wValue=0x0803/04, ?? (2bytes) + * 0x0a Master Volume, wValue=0x0200+bus[0:all + only 1..4?] data(2bytes) + * Bus Mute/Unmute wValue=0x0100+bus[0:all + only 1..4?], data(2bytes) + * 0x28 Clock source, wValue=0x0100, data={1:int,2:spdif,3:adat} (1byte) + * 0x29 Set Sample-rate, wValue=0x0100, data=sample-rate(4bytes) + * 0x32 Mixer mux, wValue=0x0600 + mixer-channel, data=input-to-connect(2bytes) + * 0x33 Output mux, wValue=bus, data=input-to-connect(2bytes) + * 0x34 Capture mux, wValue=0...18, data=input-to-connect(2bytes) + * 0x3c Matrix Mixer gains, wValue=mixer-node data=gain(2bytes) + * ?? [sometimes](4bytes, e.g 0x000003be 0x000003bf ...03ff) + * + * USB reads: (i.e. actually issued by original software) + * 0x01 wValue=0x0901+channel (1byte!!), wValue=0x0b01+channed (1byte!!) + * 0x29 wValue=0x0100 sample-rate(4bytes) + * wValue=0x0200 ?? 1byte (only once) + * 0x2a wValue=0x0100 ?? 4bytes, sample-rate2 ?? + * + * USB reads with bRequest = 0x03 = UAC2_CS_MEM + * 0x3c wValue=0x0002 1byte: sync status (locked=1) + * wValue=0x0000 18*2byte: peak meter (inputs) + * wValue=0x0001 8(?)*2byte: peak meter (mix) + * wValue=0x0003 6*2byte: peak meter (pcm/daw) + * + * USB write with bRequest = 0x03 + * 0x3c Save settings to hardware: wValue=0x005a, data=0xa5 + * + * + * <ditaa> + * /--------------\ 18chn 6chn /--------------\ + * | Hardware in +--+-------\ /------+--+ ALSA PCM out | + * --------------/ | | | | --------------/ + * | | | | + * | v v | + * | +---------------+ | + * | \ Matrix Mux / | + * | +-----+-----+ | + * | | | + * | | 18chn | + * | v | + * | +-----------+ | + * | | Mixer | | + * | | Matrix | | + * | | | | + * | | 18x6 Gain | | + * | | stages | | + * | +-----+-----+ | + * | | | + * | | | + * | 18chn | 6chn | 6chn + * v v v + * ========================= + * +---------------+ +--—------------+ + * \ Output Mux / \ Capture Mux / + * +-----+-----+ +-----+-----+ + * | | + * | 6chn | + * v | + * +-------------+ | + * | Master Gain | | + * +------+------+ | + * | | + * | 6chn | 18chn + * | (3 stereo pairs) | + * /--------------\ | | /--------------\ + * | Hardware out |<--/ -->| ALSA PCM in | + * --------------/ --------------/ + * </ditaa> + * + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> + +#include "usbaudio.h" +#include "mixer.h" +#include "helper.h" +#include "power.h" + +#include "mixer_scarlett.h" + +/* some gui mixers can't handle negative ctl values */ +#define SND_SCARLETT_LEVEL_BIAS 128 +#define SND_SCARLETT_MATRIX_IN_MAX 18 +#define SND_SCARLETT_CONTROLS_MAX 10 +#define SND_SCARLETT_OFFSETS_MAX 5 + +enum { + SCARLETT_OUTPUTS, + SCARLETT_SWITCH_IMPEDANCE, + SCARLETT_SWITCH_PAD, +}; + +enum { + SCARLETT_OFFSET_PCM = 0, + SCARLETT_OFFSET_ANALOG = 1, + SCARLETT_OFFSET_SPDIF = 2, + SCARLETT_OFFSET_ADAT = 3, + SCARLETT_OFFSET_MIX = 4, +}; + +struct scarlett_mixer_elem_enum_info { + int start; + int len; + int offsets[SND_SCARLETT_OFFSETS_MAX]; + char const * const *names; +}; + +struct scarlett_mixer_control { + unsigned char num; + unsigned char type; + const char *name; +}; + +struct scarlett_device_info { + int matrix_in; + int matrix_out; + int input_len; + int output_len; + + struct scarlett_mixer_elem_enum_info opt_master; + struct scarlett_mixer_elem_enum_info opt_matrix; + + /* initial values for matrix mux */ + int matrix_mux_init[SND_SCARLETT_MATRIX_IN_MAX]; + + int num_controls; /* number of items in controls */ + const struct scarlett_mixer_control controls[SND_SCARLETT_CONTROLS_MAX]; +}; + +/********************** Enum Strings *************************/ + +static const struct scarlett_mixer_elem_enum_info opt_pad = { + .start = 0, + .len = 2, + .offsets = {}, + .names = (char const * const []){ + "0dB", "-10dB" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_impedance = { + .start = 0, + .len = 2, + .offsets = {}, + .names = (char const * const []){ + "Line", "Hi-Z" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_clock = { + .start = 1, + .len = 3, + .offsets = {}, + .names = (char const * const []){ + "Internal", "SPDIF", "ADAT" + } +}; + +static const struct scarlett_mixer_elem_enum_info opt_sync = { + .start = 0, + .len = 2, + .offsets = {}, + .names = (char const * const []){ + "No Lock", "Locked" + } +}; + +static int scarlett_ctl_switch_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_BOOLEAN; + uinfo->count = elem->channels; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int scarlett_ctl_switch_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = !val; /* invert mute logic for mixer */ + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_switch_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i]; + val = !val; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static int scarlett_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 = (int)kctl->private_value + + SND_SCARLETT_LEVEL_BIAS; + uinfo->value.integer.step = 1; + return 0; +} + +static int scarlett_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, err, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &val); + if (err < 0) + return err; + + val = clamp(val / 256, -128, (int)kctl->private_value) + + SND_SCARLETT_LEVEL_BIAS; + ucontrol->value.integer.value[i] = val; + } + + return 0; +} + +static int scarlett_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + int i, changed = 0; + int err, oval, val; + + for (i = 0; i < elem->channels; i++) { + err = snd_usb_get_cur_mix_value(elem, i, i, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[i] - + SND_SCARLETT_LEVEL_BIAS; + val = val * 256; + if (oval != val) { + err = snd_usb_set_cur_mix_value(elem, i, i, val); + if (err < 0) + return err; + + changed = 1; + } + } + + return changed; +} + +static void scarlett_generate_name(int i, char *dst, int offsets[]) +{ + if (i > offsets[SCARLETT_OFFSET_MIX]) + sprintf(dst, "Mix %c", + 'A'+(i - offsets[SCARLETT_OFFSET_MIX] - 1)); + else if (i > offsets[SCARLETT_OFFSET_ADAT]) + sprintf(dst, "ADAT %d", i - offsets[SCARLETT_OFFSET_ADAT]); + else if (i > offsets[SCARLETT_OFFSET_SPDIF]) + sprintf(dst, "SPDIF %d", i - offsets[SCARLETT_OFFSET_SPDIF]); + else if (i > offsets[SCARLETT_OFFSET_ANALOG]) + sprintf(dst, "Analog %d", i - offsets[SCARLETT_OFFSET_ANALOG]); + else if (i > offsets[SCARLETT_OFFSET_PCM]) + sprintf(dst, "PCM %d", i - offsets[SCARLETT_OFFSET_PCM]); + else + sprintf(dst, "Off"); +} + +static int scarlett_ctl_enum_dynamic_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + unsigned int items = opt->len; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = elem->channels; + uinfo->value.enumerated.items = items; + + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + + /* generate name dynamically based on item number and offset info */ + scarlett_generate_name(uinfo->value.enumerated.item, + uinfo->value.enumerated.name, + opt->offsets); + + return 0; +} + +static int scarlett_ctl_enum_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + + return snd_ctl_enum_info(uinfo, elem->channels, opt->len, + (const char * const *)opt->names); +} + +static int scarlett_ctl_enum_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &val); + if (err < 0) + return err; + + val = clamp(val - opt->start, 0, opt->len-1); + + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int scarlett_ctl_enum_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct scarlett_mixer_elem_enum_info *opt = elem->private_data; + int err, oval, val; + + err = snd_usb_get_cur_mix_value(elem, 0, 0, &oval); + if (err < 0) + return err; + + val = ucontrol->value.integer.value[0]; + val = val + opt->start; + if (val != oval) { + snd_usb_set_cur_mix_value(elem, 0, 0, val); + return 1; + } + return 0; +} + +static int scarlett_ctl_meter_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_info *elem = kctl->private_data; + struct snd_usb_audio *chip = elem->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->id << 8); + int err; + + err = snd_usb_ctl_msg(chip->dev, + usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | + USB_DIR_IN, wValue, idx, buf, elem->channels); + if (err < 0) + return err; + + ucontrol->value.enumerated.item[0] = clamp((int)buf[0], 0, 1); + return 0; +} + +static struct snd_kcontrol_new usb_scarlett_ctl_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_switch_info, + .get = scarlett_ctl_switch_get, + .put = scarlett_ctl_switch_put, +}; + +static const DECLARE_TLV_DB_SCALE(db_scale_scarlett_gain, -12800, 100, 0); + +static struct snd_kcontrol_new usb_scarlett_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_master = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .name = "", + .info = scarlett_ctl_info, + .get = scarlett_ctl_get, + .put = scarlett_ctl_put, + .private_value = 6, /* max value */ + .tlv = { .p = db_scale_scarlett_gain } +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_dynamic_enum = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "", + .info = scarlett_ctl_enum_dynamic_info, + .get = scarlett_ctl_enum_get, + .put = scarlett_ctl_enum_put, +}; + +static struct snd_kcontrol_new usb_scarlett_ctl_sync = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .name = "", + .info = scarlett_ctl_enum_info, + .get = scarlett_ctl_meter_get, +}; + +static int add_new_ctl(struct usb_mixer_interface *mixer, + const struct snd_kcontrol_new *ncontrol, + int index, int offset, int num, + int val_type, int channels, const char *name, + const struct scarlett_mixer_elem_enum_info *opt, + struct usb_mixer_elem_info **elem_ret +) +{ + struct snd_kcontrol *kctl; + struct usb_mixer_elem_info *elem; + int err; + + elem = kzalloc(sizeof(*elem), GFP_KERNEL); + if (!elem) + return -ENOMEM; + + elem->mixer = mixer; + elem->control = offset; + elem->idx_off = num; + elem->id = index; + elem->val_type = val_type; + + elem->channels = channels; + + /* add scarlett_mixer_elem_enum_info struct */ + elem->private_data = (void *)opt; + + 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_ctl_add(mixer->chip->card, kctl); + if (err < 0) + return err; + + if (elem_ret) + *elem_ret = elem; + + return 0; +} + +static int add_output_ctls(struct usb_mixer_interface *mixer, + int index, const char *name, + const struct scarlett_device_info *info) +{ + int err; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct usb_mixer_elem_info *elem; + + /* Add mute switch */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Switch", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add volume control and initialize to 0 */ + snprintf(mx, sizeof(mx), "Master %d (%s) Playback Volume", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, + 2*index+1, USB_MIXER_S16, 2, mx, NULL, &elem); + if (err < 0) + return err; + + /* Add L channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dL (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, 0x33, 0x00, + 2*index, USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + + /* Add R channel source playback enumeration */ + snprintf(mx, sizeof(mx), "Master %dR (%s) Source Playback Enum", + index + 1, name); + err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, 0x33, 0x00, + 2*index+1, USB_MIXER_S16, 1, mx, &info->opt_master, + &elem); + if (err < 0) + return err; + + return 0; +} + +/********************** device-specific config *************************/ + +/* untested... */ +static struct scarlett_device_info s6i6_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 6, + .output_len = 6, + + .opt_master = { + .start = -1, + .len = 27, + .offsets = {0, 12, 16, 18, 18}, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .offsets = {0, 12, 16, 18, 18}, + .names = NULL + }, + + .num_controls = 0, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +/* untested... */ +static struct scarlett_device_info s8i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 8, + .output_len = 6, + + .opt_master = { + .start = -1, + .len = 25, + .offsets = {0, 12, 16, 18, 18}, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 19, + .offsets = {0, 12, 16, 18, 18}, + .names = NULL + }, + + .num_controls = 7, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 12, 13, 14, 15, /* Analog -> 1..4 */ + 16, 17, /* SPDIF -> 5,6 */ + 0, 1, 2, 3, 4, 5, 6, 7, /* PCM[1..12] -> 7..18 */ + 8, 9, 10, 11 + } +}; + +static struct scarlett_device_info s18i6_info = { + .matrix_in = 18, + .matrix_out = 6, + .input_len = 18, + .output_len = 6, + + .opt_master = { + .start = -1, + .len = 31, + .offsets = {0, 6, 14, 16, 24}, + .names = NULL, + }, + + .opt_matrix = { + .start = -1, + .len = 25, + .offsets = {0, 6, 14, 16, 24}, + .names = NULL, + }, + + .num_controls = 5, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + }, + + .matrix_mux_init = { + 6, 7, 8, 9, 10, 11, 12, 13, /* Analog -> 1..8 */ + 16, 17, 18, 19, 20, 21, /* ADAT[1..6] -> 9..14 */ + 14, 15, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static struct scarlett_device_info s18i8_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 8, + + .opt_master = { + .start = -1, + .len = 35, + .offsets = {0, 8, 16, 18, 26}, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 27, + .offsets = {0, 8, 16, 18, 26}, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Headphone 1" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Headphone 2" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + }, + + .matrix_mux_init = { + 8, 9, 10, 11, 12, 13, 14, 15, /* Analog -> 1..8 */ + 18, 19, 20, 21, 22, 23, /* ADAT[1..6] -> 9..14 */ + 16, 17, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + +static struct scarlett_device_info s18i20_info = { + .matrix_in = 18, + .matrix_out = 8, + .input_len = 18, + .output_len = 20, + + .opt_master = { + .start = -1, + .len = 47, + .offsets = {0, 20, 28, 30, 38}, + .names = NULL + }, + + .opt_matrix = { + .start = -1, + .len = 39, + .offsets = {0, 20, 28, 30, 38}, + .names = NULL + }, + + .num_controls = 10, + .controls = { + { .num = 0, .type = SCARLETT_OUTPUTS, .name = "Monitor" }, + { .num = 1, .type = SCARLETT_OUTPUTS, .name = "Line 3/4" }, + { .num = 2, .type = SCARLETT_OUTPUTS, .name = "Line 5/6" }, + { .num = 3, .type = SCARLETT_OUTPUTS, .name = "Line 7/8" }, + { .num = 4, .type = SCARLETT_OUTPUTS, .name = "Line 9/10" }, + { .num = 5, .type = SCARLETT_OUTPUTS, .name = "SPDIF" }, + { .num = 6, .type = SCARLETT_OUTPUTS, .name = "ADAT 1/2" }, + { .num = 7, .type = SCARLETT_OUTPUTS, .name = "ADAT 3/4" }, + { .num = 8, .type = SCARLETT_OUTPUTS, .name = "ADAT 5/6" }, + { .num = 9, .type = SCARLETT_OUTPUTS, .name = "ADAT 7/8" }, + /*{ .num = 1, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 1, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_IMPEDANCE, .name = NULL}, + { .num = 2, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 3, .type = SCARLETT_SWITCH_PAD, .name = NULL}, + { .num = 4, .type = SCARLETT_SWITCH_PAD, .name = NULL},*/ + }, + + .matrix_mux_init = { + 20, 21, 22, 23, 24, 25, 26, 27, /* Analog -> 1..8 */ + 30, 31, 32, 33, 34, 35, /* ADAT[1..6] -> 9..14 */ + 28, 29, /* SPDIF -> 15,16 */ + 0, 1 /* PCM[1,2] -> 17,18 */ + } +}; + + +static int scarlett_controls_create_generic(struct usb_mixer_interface *mixer, + struct scarlett_device_info *info) +{ + int i, err; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + const struct scarlett_mixer_control *ctl; + struct usb_mixer_elem_info *elem; + + /* create master switch and playback volume */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_switch, 0x0a, 0x01, 0, + USB_MIXER_S16, 1, "Master Playback Switch", NULL, + &elem); + if (err < 0) + return err; + + err = add_new_ctl(mixer, &usb_scarlett_ctl_master, 0x0a, 0x02, 0, + USB_MIXER_S16, 1, "Master Playback Volume", NULL, + &elem); + if (err < 0) + return err; + + /* iterate through controls in info struct and create each one */ + for (i = 0; i < info->num_controls; i++) { + ctl = &info->controls[i]; + + switch (ctl->type) { + case SCARLETT_OUTPUTS: + err = add_output_ctls(mixer, ctl->num, ctl->name, info); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_IMPEDANCE: + sprintf(mx, "Input %d Impedance Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x09, ctl->num, USB_MIXER_S16, 1, mx, + &opt_impedance, &elem); + if (err < 0) + return err; + break; + case SCARLETT_SWITCH_PAD: + sprintf(mx, "Input %d Pad Switch", ctl->num); + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x01, + 0x0b, ctl->num, USB_MIXER_S16, 1, mx, + &opt_pad, &elem); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* + * Create and initialize a mixer for the Focusrite(R) Scarlett + */ +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer) +{ + int err, i, o; + char mx[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + struct scarlett_device_info *info; + struct usb_mixer_elem_info *elem; + static char sample_rate_buffer[4] = { '\x80', '\xbb', '\x00', '\x00' }; + + /* only use UAC_VERSION_2 */ + if (!mixer->protocol) + return 0; + + switch (mixer->chip->usb_id) { + case USB_ID(0x1235, 0x8012): + info = &s6i6_info; + break; + case USB_ID(0x1235, 0x8002): + info = &s8i6_info; + break; + case USB_ID(0x1235, 0x8004): + info = &s18i6_info; + break; + case USB_ID(0x1235, 0x8014): + info = &s18i8_info; + break; + case USB_ID(0x1235, 0x800c): + info = &s18i20_info; + break; + default: /* device not (yet) supported */ + return -EINVAL; + } + + /* generic function to create controls */ + err = scarlett_controls_create_generic(mixer, info); + if (err < 0) + return err; + + /* setup matrix controls */ + for (i = 0; i < info->matrix_in; i++) { + snprintf(mx, sizeof(mx), "Matrix %02d Input Playback Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, 0x32, + 0x06, i, USB_MIXER_S16, 1, mx, + &info->opt_matrix, &elem); + if (err < 0) + return err; + + for (o = 0; o < info->matrix_out; o++) { + sprintf(mx, "Matrix %02d Mix %c Playback Volume", i+1, + o+'A'); + err = add_new_ctl(mixer, &usb_scarlett_ctl, 0x3c, 0x00, + (i << 3) + (o & 0x07), USB_MIXER_S16, + 1, mx, NULL, &elem); + if (err < 0) + return err; + + } + } + + for (i = 0; i < info->input_len; i++) { + snprintf(mx, sizeof(mx), "Input Source %02d Capture Route", + i+1); + err = add_new_ctl(mixer, &usb_scarlett_ctl_dynamic_enum, 0x34, + 0x00, i, USB_MIXER_S16, 1, mx, + &info->opt_master, &elem); + if (err < 0) + return err; + } + + /* val_len == 1 needed here */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_enum, 0x28, 0x01, 0, + USB_MIXER_U8, 1, "Sample Clock Source", + &opt_clock, &elem); + if (err < 0) + return err; + + /* val_len == 1 and UAC2_CS_MEM */ + err = add_new_ctl(mixer, &usb_scarlett_ctl_sync, 0x3c, 0x00, 2, + USB_MIXER_U8, 1, "Sample Clock Sync Status", + &opt_sync, &elem); + if (err < 0) + return err; + + /* initialize sampling rate to 48000 */ + 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) | + (0x29 << 8), sample_rate_buffer, 4); + if (err < 0) + return err; + + return err; +} diff --git a/sound/usb/mixer_scarlett.h b/sound/usb/mixer_scarlett.h new file mode 100644 index 0000000..19c592a --- /dev/null +++ b/sound/usb/mixer_scarlett.h @@ -0,0 +1,6 @@ +#ifndef __USB_MIXER_SCARLETT_H +#define __USB_MIXER_SCARLETT_H + +int snd_scarlett_controls_create(struct usb_mixer_interface *mixer); + +#endif /* __USB_MIXER_SCARLETT_H */
At Wed, 12 Nov 2014 12:06:58 -0600, Chris J Arges wrote:
This is v5 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
Already v7? :)
I applied the patch series now to for-next branch. If anything is missing, let's fix on top of that.
thanks,
Takashi
[v3]
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
[v4]
This version removes the per-mixer control creation functions and uses a generic function based on structure data. Macros used for control addition are removed and the plain function is used instead. Hardcoded text block is removed and macros to define strings are used instead. Hardcoded control initialization has been removed.
[v5]
In this version, HW saving functionality has been removed in this initial patchset. Macros for function calls are removed for readability. Strings for enums are created dynamically using the info structures. String lengths for controls are now all SNDRV_CTL_ELEM_ID_NAME_MAXLEN in length.
[v6]
Removed dead variables. Made names const char * const * again. Adjusted scarlett_ctl_enum_* functions to be a bit clearer. Used strlcpy where appropriate. Properly free two dimensional array.
[v7]
Generate strings directly in enum_info function instead of dynamically allocating into another array and copying. Remove unnecessary initializations, kfrees and use kmalloc instead of kmalloc_array. Update comments to reflect current mixer functionality.
Chris J Arges (4): Revert "ALSA: usb-audio: Add quirk for Focusrite Scarlett ALSA: usb-audio: Add private_data pointer to usb_mixer_elem_info ALSA: usb-audio: make set_*_mix_values functions public ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
sound/usb/Makefile | 1 + sound/usb/mixer.c | 34 +- sound/usb/mixer.h | 9 + sound/usb/mixer_quirks.c | 18 +- sound/usb/mixer_scarlett.c | 1001 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + sound/usb/quirks-table.h | 51 --- 7 files changed, 1044 insertions(+), 76 deletions(-) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
-- 2.1.3
On 2014-11-13 07:36, Takashi Iwai wrote:
At Wed, 12 Nov 2014 12:06:58 -0600, Chris J Arges wrote:
This is v5 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
Already v7? :)
I applied the patch series now to for-next branch. If anything is missing, let's fix on top of that.
\o/
Excellent, thanks to all of you for bringing better hardware support to Linux! :-)
On 11/13/2014 12:36 AM, Takashi Iwai wrote:
At Wed, 12 Nov 2014 12:06:58 -0600, Chris J Arges wrote:
This is v5 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
Already v7? :)
I applied the patch series now to for-next branch. If anything is missing, let's fix on top of that.
thanks,
Takashi
Takashi, Thank you for your patience and help reviewing and applying this. --chris j arges
[v3]
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
[v4]
This version removes the per-mixer control creation functions and uses a generic function based on structure data. Macros used for control addition are removed and the plain function is used instead. Hardcoded text block is removed and macros to define strings are used instead. Hardcoded control initialization has been removed.
[v5]
In this version, HW saving functionality has been removed in this initial patchset. Macros for function calls are removed for readability. Strings for enums are created dynamically using the info structures. String lengths for controls are now all SNDRV_CTL_ELEM_ID_NAME_MAXLEN in length.
[v6]
Removed dead variables. Made names const char * const * again. Adjusted scarlett_ctl_enum_* functions to be a bit clearer. Used strlcpy where appropriate. Properly free two dimensional array.
[v7]
Generate strings directly in enum_info function instead of dynamically allocating into another array and copying. Remove unnecessary initializations, kfrees and use kmalloc instead of kmalloc_array. Update comments to reflect current mixer functionality.
Chris J Arges (4): Revert "ALSA: usb-audio: Add quirk for Focusrite Scarlett ALSA: usb-audio: Add private_data pointer to usb_mixer_elem_info ALSA: usb-audio: make set_*_mix_values functions public ALSA: usb-audio: Scarlett mixer interface for 6i6, 18i6, 18i8 and 18i20
sound/usb/Makefile | 1 + sound/usb/mixer.c | 34 +- sound/usb/mixer.h | 9 + sound/usb/mixer_quirks.c | 18 +- sound/usb/mixer_scarlett.c | 1001 ++++++++++++++++++++++++++++++++++++++++++++ sound/usb/mixer_scarlett.h | 6 + sound/usb/quirks-table.h | 51 --- 7 files changed, 1044 insertions(+), 76 deletions(-) create mode 100644 sound/usb/mixer_scarlett.c create mode 100644 sound/usb/mixer_scarlett.h
-- 2.1.3
On 2014-11-03 23:58, Chris J Arges wrote:
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Thanks a lot for taking over - it surely has been done with better speed than I would have.
- Code cleanup:
- David Henningsson <david.henningsson at canonical.com>
Thanks for the credit, not sure I deserve it though, all I did was to squash the patches and fix the checkpatch errors :-)
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0] > 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c << 8), buf, 2);
if (err < 0)
return err;
usb_audio_info(elem->mixer->chip,
"scarlett: saved settings to hardware.\n");
- }
- return 0;
+}
To elaborate on Takashi's concern about this control, imagine e g what alsactl restore/store being run on startup/shutdown. On shutdown, alsactl store would read "0", and on startup, alsactl restore would write "0", with the result that the hw would not store the values set.
Would it be possible to have an autosave instead? E g, whenever a value is changed, it saves that value to the hardware. That's how other hardware works and what userspace expects. If you want to avoid a lot of autosaves, e g if the autosave takes a lot of time before the hw responds, maybe you could do the autosave in a 10 ms delayed_work, or similar.
On 11/04/2014 02:11 PM, David Henningsson wrote:
On 2014-11-03 23:58, Chris J Arges wrote:
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Thanks a lot for taking over - it surely has been done with better speed than I would have.
- Code cleanup:
- David Henningsson <david.henningsson at canonical.com>
Thanks for the credit, not sure I deserve it though, all I did was to squash the patches and fix the checkpatch errors :-)
: )
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0] > 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c << 8), buf, 2);
if (err < 0)
return err;
usb_audio_info(elem->mixer->chip,
"scarlett: saved settings to hardware.\n");
- }
- return 0;
+}
To elaborate on Takashi's concern about this control, imagine e g what alsactl restore/store being run on startup/shutdown. On shutdown, alsactl store would read "0", and on startup, alsactl restore would write "0", with the result that the hw would not store the values set.
Would it be possible to have an autosave instead? E g, whenever a value is changed, it saves that value to the hardware. That's how other hardware works and what userspace expects. If you want to avoid a lot of autosaves, e g if the autosave takes a lot of time before the hw responds, maybe you could do the autosave in a 10 ms delayed_work, or similar.
David, Hey, as Tobias mentioned this is a HW saving (to the mixer's NVRAM) function used for using the mixer disconnected from a computer. We wouldn't want to continually write the NVRAM on every control update as I'm unsure of how many write-cycles the device is capable of.
So without it you can still plug the mixer into the computer and alsa will restore any saved settings automatically. I'm planning on dropping the HW Save in v5 until we can figure out a proper control mechanism for this.
--chris
On 2014-11-04 21:18, Chris J Arges wrote:
On 11/04/2014 02:11 PM, David Henningsson wrote:
On 2014-11-03 23:58, Chris J Arges wrote:
This code contains the Scarlett mixer interface code that was originally written by Tobias Hoffman and Robin Gareus. Because the device doesn't properly implement UAC2 this code adds a mixer quirk for the device.
Thanks a lot for taking over - it surely has been done with better speed than I would have.
- Code cleanup:
- David Henningsson <david.henningsson at canonical.com>
Thanks for the credit, not sure I deserve it though, all I did was to squash the patches and fix the checkpatch errors :-)
: )
+static int scarlett_ctl_save_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *ucontrol)
+{
- struct usb_mixer_elem_info *elem = kctl->private_data;
- struct snd_usb_audio *chip = elem->mixer->chip;
- char buf[] = { 0x00, 0xa5 };
- int err;
- if (ucontrol->value.enumerated.item[0] > 0) {
err = snd_usb_ctl_msg(chip->dev,
usb_sndctrlpipe(chip->dev, 0), UAC2_CS_MEM,
USB_RECIP_INTERFACE | USB_TYPE_CLASS |
USB_DIR_OUT, 0x005a, snd_usb_ctrl_intf(chip) |
(0x3c << 8), buf, 2);
if (err < 0)
return err;
usb_audio_info(elem->mixer->chip,
"scarlett: saved settings to hardware.\n");
- }
- return 0;
+}
To elaborate on Takashi's concern about this control, imagine e g what alsactl restore/store being run on startup/shutdown. On shutdown, alsactl store would read "0", and on startup, alsactl restore would write "0", with the result that the hw would not store the values set.
Would it be possible to have an autosave instead? E g, whenever a value is changed, it saves that value to the hardware. That's how other hardware works and what userspace expects. If you want to avoid a lot of autosaves, e g if the autosave takes a lot of time before the hw responds, maybe you could do the autosave in a 10 ms delayed_work, or similar.
David, Hey, as Tobias mentioned this is a HW saving (to the mixer's NVRAM) function used for using the mixer disconnected from a computer. We wouldn't want to continually write the NVRAM on every control update as I'm unsure of how many write-cycles the device is capable of.
So without it you can still plug the mixer into the computer and alsa will restore any saved settings automatically. I'm planning on dropping the HW Save in v5 until we can figure out a proper control mechanism for this.
Aha, sorry for not grasping that. Maybe "save to HW" could be labelled "save for offline use" or so. (Cool feature, btw. I didn't know that was possible.)
I think a sysfs node would be simplest for this case. Just like we do "echo 1 > /sys/class/sound/hwC0D0/reconfig" to tell the HDA driver to reconfigure itself, there could be a "save_mixer_for_offline_use" node for this feature.
Hi Chris,
On Wednesday 29 October 2014 15:55:59 Chris J Arges wrote:
This is v3 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
I have only tested this on my own device which is a Focusrite Scarlett 18i8, any additional testing would be appreciated.
I have a 6i6, and ran `alsa-info.sh` on the following configurations:
Kernel without patch: - kernel-3.11 (openSUSE) - output of alsa-info.sh: http://paste.kde.org/ps7bionk7
Kernel with patch (v3): - kernel-3.18-rc2, self-compiled following [1] - output of alsa-info.sh: http://paste.kde.org/pjb8wgbxe (starting from line 999)
I'm not too familiar with interpreting this output. It seems to me that a lot of mixer controls are listed (maybe even too many for the 6i6?) If you need more information, or if I should test something specific, please let me know.
What I found when rebooting into my old kernel again, the internal state of the Scarlett Focusrite 6i6 changed, meaning that I had to turn the device off and on again to reset it to the stored state. Not sure whether this is related to the patch, though?!
Greetings, Dominik
[1] https://www.suse.com/communities/conversations/compiling-de-linux-kernel-sus...
On 11/02/2014 01:00 PM, Dominik Haumann wrote:
Hi Chris,
On Wednesday 29 October 2014 15:55:59 Chris J Arges wrote:
This is v3 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
I have only tested this on my own device which is a Focusrite Scarlett 18i8, any additional testing would be appreciated.
I have a 6i6, and ran `alsa-info.sh` on the following configurations:
Kernel without patch:
- kernel-3.11 (openSUSE)
- output of alsa-info.sh: http://paste.kde.org/ps7bionk7
Kernel with patch (v3):
- kernel-3.18-rc2, self-compiled following [1]
- output of alsa-info.sh: http://paste.kde.org/pjb8wgbxe (starting from line 999)
I'm not too familiar with interpreting this output. It seems to me that a lot of mixer controls are listed (maybe even too many for the 6i6?) If you need more information, or if I should test something specific, please let me know.
I believe this looks as it should (although I don't have this device), there are a lot of controls to be able to use the matrix mixing functionality. So this allows you to create separate mixes for various mix buses. The way this is setup in the alsamixer is there is an Matrix X Input which one can select which input one wants to mix, and then a series of Mixes A-H which correspond to output volume.
Testing various inputs and outputs to see if this is working as expected would be very helpful.
What I found when rebooting into my old kernel again, the internal state of the Scarlett Focusrite 6i6 changed, meaning that I had to turn the device off and on again to reset it to the stored state. Not sure whether this is related to the patch, though?!
I've noticed this too even with the original patch. The code currently does initialize values when it starts, and even if I remove those initializations it initializes to some not very useful defaults. If I manually power on and off the device it goes to the stored default. The stored default can be modified by using the 'Save to HW' control; and this does work.
--chris
Greetings, Dominik
[1] https://www.suse.com/communities/conversations/compiling-de-linux-kernel-sus...
On 11/03/2014 09:49 AM, Chris J Arges wrote:
On 11/02/2014 01:00 PM, Dominik Haumann wrote:
Hi Chris,
On Wednesday 29 October 2014 15:55:59 Chris J Arges wrote:
This is v3 of the patchset to merge what Tobias Hoffman and Robin Gareus have done to enable the Focusrite Scarlett mixers for use with ALSA.
I have split the commits into hopefully a logical series. First the original quirk is reverted for one model of a Scarlett device. Next an additional structure is added to be able to more easily reuse usb_mixer_elem_info. After this mixer functions that were useful to this code were made public. Finally the last patch adds the necessary functions to make this mixer work.
I have only tested this on my own device which is a Focusrite Scarlett 18i8, any additional testing would be appreciated.
I have a 6i6, and ran `alsa-info.sh` on the following configurations:
Kernel without patch:
- kernel-3.11 (openSUSE)
- output of alsa-info.sh: http://paste.kde.org/ps7bionk7
Kernel with patch (v3):
- kernel-3.18-rc2, self-compiled following [1]
- output of alsa-info.sh: http://paste.kde.org/pjb8wgbxe (starting from line 999)
I'm not too familiar with interpreting this output. It seems to me that a lot of mixer controls are listed (maybe even too many for the 6i6?) If you need more information, or if I should test something specific, please let me know.
I believe this looks as it should (although I don't have this device), there are a lot of controls to be able to use the matrix mixing functionality. So this allows you to create separate mixes for various mix buses. The way this is setup in the alsamixer is there is an Matrix X Input which one can select which input one wants to mix, and then a series of Mixes A-H which correspond to output volume.
Testing various inputs and outputs to see if this is working as expected would be very helpful.
What I found when rebooting into my old kernel again, the internal state of the Scarlett Focusrite 6i6 changed, meaning that I had to turn the device off and on again to reset it to the stored state. Not sure whether this is related to the patch, though?!
I've noticed this too even with the original patch. The code currently does initialize values when it starts, and even if I remove those initializations it initializes to some not very useful defaults. If I manually power on and off the device it goes to the stored default. The stored default can be modified by using the 'Save to HW' control; and this does work.
Just as an FYI, I'm going to remove the force initialization stuff since, it would be best to allow things like 'alsactl store' to work to save the mixer settings for the user.
I'll post v3 soon.
--chris
Greetings, Dominik
[1] https://www.suse.com/communities/conversations/compiling-de-linux-kernel-sus...
At Tue, 21 Oct 2014 14:46:26 -0500, Chris J Arges wrote:
This is a followup to David Henningsson's PATCH [1] to include the work of Tobias Hoffman and Robin Gareus in getting the Scarlett mixer interface working. I've read through the comments and tried to include fixes where appropriate.
Overall, I'd like to get a good base commit and start working on other commits that address any major structual changes such as making functions more generic or any other cleanup.
Use 3.18-rc1 as the baseline.
I've tested this on my Scarlett 18i8 mixer and it does work, however there are still a few issues which need to be fixed:
- When loading a device one gets the following messages:
snd-usb-audio 1-1.1:1.0: control 2:0:0:Master Playback Switch:0 is already present snd-usb-audio: probe of 1-1.1:1.3 failed with error -5
This is certainly a driver problem, as the message says.
- When unloading a device there are numerous sysfs_remove_group issues:
usb 1-1.1: USB disconnect, device number 6 ------------[ cut here ]------------ WARNING: CPU: 0 PID: 54 at /build/buildd/linux-3.16.0/fs/sysfs/group.c:219 sysfs_remove_group+0x99/0xa0() sysfs group ffffffff82cbd6e0 not found for kobject 'midi1'
(snip)
However this issue occured even without the patch applied.
Yes, there was a similar report, but we couldn't reproduce here. It's a Oops coming from OSS midi device. This is fairly harmless, but annoying. Could you give your kernel config? Maybe there's some difference.
Takashi
At Wed, 22 Oct 2014 08:36:50 +0200, Takashi Iwai wrote:
- When unloading a device there are numerous sysfs_remove_group issues:
usb 1-1.1: USB disconnect, device number 6 ------------[ cut here ]------------ WARNING: CPU: 0 PID: 54 at /build/buildd/linux-3.16.0/fs/sysfs/group.c:219 sysfs_remove_group+0x99/0xa0() sysfs group ffffffff82cbd6e0 not found for kobject 'midi1'
(snip)
However this issue occured even without the patch applied.
Yes, there was a similar report, but we couldn't reproduce here. It's a Oops coming from OSS midi device. This is fairly harmless, but annoying. Could you give your kernel config? Maybe there's some difference.
I'm now checking this issue again, and I still couldn't reproduce it on my local machines. Could you give your kernel config if this still happens?
Also, as a blind shot: does the patch below give any difference?
thanks,
Takashi
--- diff --git a/sound/usb/card.c b/sound/usb/card.c index 69725d5fa2d6..475961afc886 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -595,13 +595,13 @@ static void usb_audio_disconnect(struct usb_interface *intf) up_write(&chip->shutdown_rwsem);
mutex_lock(®ister_mutex); + snd_card_disconnect(card); chip->num_interfaces--; if (chip->num_interfaces <= 0) { struct snd_usb_stream *as; struct snd_usb_endpoint *ep; struct usb_mixer_interface *mixer;
- snd_card_disconnect(card); /* release the pcm resources */ list_for_each_entry(as, &chip->pcm_list, list) { snd_usb_stream_disconnect(as);
At Wed, 05 Nov 2014 12:33:37 +0100, Takashi Iwai wrote:
At Wed, 22 Oct 2014 08:36:50 +0200, Takashi Iwai wrote:
- When unloading a device there are numerous sysfs_remove_group issues:
usb 1-1.1: USB disconnect, device number 6 ------------[ cut here ]------------ WARNING: CPU: 0 PID: 54 at /build/buildd/linux-3.16.0/fs/sysfs/group.c:219 sysfs_remove_group+0x99/0xa0() sysfs group ffffffff82cbd6e0 not found for kobject 'midi1'
(snip)
However this issue occured even without the patch applied.
Yes, there was a similar report, but we couldn't reproduce here. It's a Oops coming from OSS midi device. This is fairly harmless, but annoying. Could you give your kernel config? Maybe there's some difference.
I'm now checking this issue again, and I still couldn't reproduce it on my local machines. Could you give your kernel config if this still happens?
Also, as a blind shot: does the patch below give any difference?
Try the one below instead. This is for 3.18 but should be applicable to older kernels, too. (It's not applicable to for-next branch due to the recent cleanups, though.)
Takashi
--- diff --git a/sound/usb/card.c b/sound/usb/card.c index 7ecd0e8a5c51..f61ebb17cc64 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -591,18 +591,19 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, { struct snd_card *card; struct list_head *p; + bool was_shutdown;
if (chip == (void *)-1L) return;
card = chip->card; down_write(&chip->shutdown_rwsem); + was_shutdown = chip->shutdown; chip->shutdown = 1; up_write(&chip->shutdown_rwsem);
mutex_lock(®ister_mutex); - chip->num_interfaces--; - if (chip->num_interfaces <= 0) { + if (!was_shutdown) { struct snd_usb_endpoint *ep;
snd_card_disconnect(card); @@ -622,6 +623,10 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, list_for_each(p, &chip->mixer_list) { snd_usb_mixer_disconnect(p); } + } + + chip->num_interfaces--; + if (chip->num_interfaces <= 0) { usb_chip[chip->index] = NULL; mutex_unlock(®ister_mutex); snd_card_free_when_closed(card);
At Wed, 05 Nov 2014 13:39:30 +0100, Takashi Iwai wrote:
At Wed, 05 Nov 2014 12:33:37 +0100, Takashi Iwai wrote:
At Wed, 22 Oct 2014 08:36:50 +0200, Takashi Iwai wrote:
- When unloading a device there are numerous sysfs_remove_group issues:
usb 1-1.1: USB disconnect, device number 6 ------------[ cut here ]------------ WARNING: CPU: 0 PID: 54 at /build/buildd/linux-3.16.0/fs/sysfs/group.c:219 sysfs_remove_group+0x99/0xa0() sysfs group ffffffff82cbd6e0 not found for kobject 'midi1'
(snip)
However this issue occured even without the patch applied.
Yes, there was a similar report, but we couldn't reproduce here. It's a Oops coming from OSS midi device. This is fairly harmless, but annoying. Could you give your kernel config? Maybe there's some difference.
I'm now checking this issue again, and I still couldn't reproduce it on my local machines. Could you give your kernel config if this still happens?
Also, as a blind shot: does the patch below give any difference?
Try the one below instead. This is for 3.18 but should be applicable to older kernels, too. (It's not applicable to for-next branch due to the recent cleanups, though.)
The patch was confirmed to fix, at least, for 3.14 kernel: https://bugzilla.kernel.org/show_bug.cgi?id=80931
I'll resubmit a proper patch to ML.
Takashi
On 11/05/2014 06:39 AM, Takashi Iwai wrote:
At Wed, 05 Nov 2014 12:33:37 +0100, Takashi Iwai wrote:
At Wed, 22 Oct 2014 08:36:50 +0200, Takashi Iwai wrote:
- When unloading a device there are numerous sysfs_remove_group issues:
usb 1-1.1: USB disconnect, device number 6 ------------[ cut here ]------------ WARNING: CPU: 0 PID: 54 at /build/buildd/linux-3.16.0/fs/sysfs/group.c:219 sysfs_remove_group+0x99/0xa0() sysfs group ffffffff82cbd6e0 not found for kobject 'midi1'
(snip)
However this issue occured even without the patch applied.
Yes, there was a similar report, but we couldn't reproduce here. It's a Oops coming from OSS midi device. This is fairly harmless, but annoying. Could you give your kernel config? Maybe there's some difference.
I'm now checking this issue again, and I still couldn't reproduce it on my local machines. Could you give your kernel config if this still happens?
Also, as a blind shot: does the patch below give any difference?
Try the one below instead. This is for 3.18 but should be applicable to older kernels, too. (It's not applicable to for-next branch due to the recent cleanups, though.)
I've tested this against 3.18-rc3 on my laptop and it no longer produces any warnings on disconnection! Thanks, --chris
Takashi
diff --git a/sound/usb/card.c b/sound/usb/card.c index 7ecd0e8a5c51..f61ebb17cc64 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -591,18 +591,19 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, { struct snd_card *card; struct list_head *p;
bool was_shutdown;
if (chip == (void *)-1L) return;
card = chip->card; down_write(&chip->shutdown_rwsem);
was_shutdown = chip->shutdown; chip->shutdown = 1; up_write(&chip->shutdown_rwsem);
mutex_lock(®ister_mutex);
- chip->num_interfaces--;
- if (chip->num_interfaces <= 0) {
if (!was_shutdown) { struct snd_usb_endpoint *ep;
snd_card_disconnect(card);
@@ -622,6 +623,10 @@ static void snd_usb_audio_disconnect(struct usb_device *dev, list_for_each(p, &chip->mixer_list) { snd_usb_mixer_disconnect(p); }
- }
- chip->num_interfaces--;
- if (chip->num_interfaces <= 0) { usb_chip[chip->index] = NULL; mutex_unlock(®ister_mutex); snd_card_free_when_closed(card);
participants (6)
-
Chris J Arges
-
Clemens Ladisch
-
David Henningsson
-
Dominik Haumann
-
Takashi Iwai
-
Tobias Hoffmann