[alsa-devel] [PATCH] ALSA: usb-audio: Add support for Presonus Studio 1810c
Takashi Iwai
tiwai at suse.de
Fri Dec 27 09:48:22 CET 2019
On Wed, 25 Dec 2019 03:34:24 +0100,
mickflemm at gmail.com wrote:
>
> This patch adds support for Presonus Studio 1810c, a usb interface
> that's UAC2 compliant with a few quirks and a few extra hw-specific
> controls. I've tested all 3 altsettings and the added switch
> controls and they work as expected.
>
> More infos on the card:
> https://www.presonus.com/products/Studio-1810c
>
> Note that this work is based on packet inspection with
> usbmon. I just wanted to get this card to work for using
> it on our open-source radio station:
> https://github.com/UoC-Radio
>
> Signed-off-by: Nick Kossifidis <mickflemm at gmail.com>
Thanks for the patch.
In addition to the issues kbuild bot suggested, below are my quick
review comments:
> diff --git a/sound/usb/format.c b/sound/usb/format.c
> index d79db7130..edf3f2a55 100644
> --- a/sound/usb/format.c
> +++ b/sound/usb/format.c
> @@ -262,6 +262,23 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip,
> }
>
> for (rate = min; rate <= max; rate += res) {
> +
> + /*
> + * Presonus Studio 1810c anounces invalid
> + * sampling rates for its streams.
> + */
> + if (chip->usb_id == USB_ID(0x0194f, 0x010c) &&
> + ((rate > 48000 && fp->altsetting == 1) ||
> + ((rate < 88200 || rate > 96000)
> + && fp->altsetting == 2) ||
> + ((rate < 176400 || rate > 192000)
> + && fp->altsetting == 3))) {
> + if (res)
> + continue;
> + else
> + break;
> + }
It's hard to imagine what result this would lead to, because the
conditions are so complex.
Maybe better to prepare a fixed table instead? Or, can we simply take
a fixed quirk?
> diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c
> new file mode 100644
> index 000000000..ff86e4aab
> --- /dev/null
> +++ b/sound/usb/mixer_s1810c.c
> @@ -0,0 +1,565 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Presonus Studio 1810c driver for ALSA
> + * Copyright (C) 2019 Nick Kossifidis <mickflemm at gmail.com>
> + *
> + * Based on reverse engineering of the communication protocol
> + * between the windows driver / Univeral Control (UC) program
> + * and the device, through usbmon.
> + *
> + * For now this bypasses the mixer, with all channels split,
> + * so that the software can mix with greater flexibility.
> + * It also adds controls for the 4 buttons on the front of
> + * the device.
> + */
Usually place a blank line here.
> +#include <linux/usb.h>
> +#include <linux/usb/audio-v2.h>
> +#include <sound/control.h>
> +#include <linux/slab.h>
The common practice is to group linux/*.h first, followed by
sound/*.h. And <sound/core.h> is almost always needed before other
sound/*.h.
> +struct s1810c_ctl_packet {
> + uint32_t a;
> + uint32_t b;
> + uint32_t fixed1;
> + uint32_t fixed2;
> + uint32_t c;
> + uint32_t d;
> + uint32_t e;
Use u32, u16 or such types instead of uint*_t.
It's the standard type for kernel codes.
Also applied in other places in the patch, too.
> +static int
> +snd_sc1810c_get_status_field(struct usb_device *dev,
> + uint32_t *field, int field_idx, uint16_t *seqnum)
> +{
> + struct s1810c_state_packet pkt_out = { 0 };
> + struct s1810c_state_packet pkt_in = { 0 };
> + int ret = 0;
> +
> + pkt_out.fields[SC1810C_STATE_F1_IDX] = SC1810C_SET_STATE_F1;
> + pkt_out.fields[SC1810C_STATE_F2_IDX] = SC1810C_SET_STATE_F2;
> + ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
> + SC1810C_SET_STATE_REQ,
> + SC1810C_SET_STATE_REQTYPE,
> + (*seqnum), 0, &pkt_out, sizeof(pkt_out));
Avoid unnecessary parentheses around *seqnum.
Ditto for other possible places.
> +static int
> +snd_s1810c_switch_set(struct snd_kcontrol *kctl,
> + struct snd_ctl_elem_value *ctl_elem)
> +{
> + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kctl);
> + struct usb_mixer_interface *mixer = list->mixer;
> + uint32_t curval = 0;
> + uint32_t newval = (uint32_t) ctl_elem->value.integer.value[0];
> + int ret = 0;
> +
> + ret = snd_s1810c_get_switch_state(mixer, kctl, &curval);
> + if (ret < 0)
> + return 0;
> +
> + if (curval == newval)
> + return 0;
> +
> + kctl->private_value &= ~(0x1 << 16);
> + kctl->private_value |= (unsigned int)(newval & 0x1) << 16;
> + ret = snd_s1810c_set_switch_state(mixer, kctl);
Hm, this can be racy. The control get/put isn't protected, so you
might get the inconsistency here when multiple kctls are accessed
concurrently.
> +static int
> +snd_s1810c_line_sw_info(struct snd_kcontrol *kctl,
> + struct snd_ctl_elem_info *uinfo)
> +{
> + static const char *const texts[2] = { "Preamp on (Mic/Inst)",
> + "Preamp off (Line in)"
> + };
Better to put "Preamp on..." item in the next line.
> +static struct snd_kcontrol_new snd_s1810c_line_sw = {
This (and other following definitions) can be const?
> + .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> + .name = "Line 1/2 source type",
The control name should consist of words starting with a capital
letter, so it should be "Line 1/2 Source Type".
Also, usually a control name needs a standard suffix. Though, this
has a special return enum type, so it can be OK as is.
However...
> + .info = snd_s1810c_line_sw_info,
> + .get = snd_s1810c_switch_get,
> + .put = snd_s1810c_switch_set,
... this shows that the combination is invalid. The enum type doesn't
get/put the values in integer fields. It's incompatible.
> + .private_value = (SC1810C_STATE_LINE_SW | SC1810C_CTL_LINE_SW << 8)
> +};
> +
> +static struct snd_kcontrol_new snd_s1810c_mute_sw = {
> + .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> + .name = "Mute Main Out",
... and this one, for example, should deserve for "Switch" suffix, as
it's a standard boolean switch (which use integer fields unlike
enum).
> +/* Entry point, called from mixer_quirks.c */
> +int snd_sc1810_init_mixer(struct usb_mixer_interface *mixer)
> +{
> + struct s1810_mixer_state *private = NULL;
> + struct snd_usb_audio *chip = mixer->chip;
> + struct usb_device *dev = chip->dev;
> + int ret = 0;
> +
> + /* Run this only once */
> + if (!list_empty(&chip->mixer_list))
> + return 0;
> +
> + dev_info(&dev->dev,
> + "Presonus Studio 1810c, device_setup: %u\n", chip->setup);
> + if (chip->setup == 1)
> + dev_info(&dev->dev, "(8out/18in @ 48KHz)\n");
> + else if (chip->setup == 2)
> + dev_info(&dev->dev, "(6out/8in @ 192KHz)\n");
> + else
> + dev_info(&dev->dev, "(8out/14in @ 96KHz)\n");
> +
> + ret = snd_s1810c_init_mixer_maps(chip);
> + if (ret < 0)
> + return ret;
> +
> + private = vzalloc(sizeof(struct s1810_mixer_state));
I don't think vmalloc is needed here, as the object is so small.
Use kzalloc() and kfree() instead (unless I overlook something).
> + if (!private) {
> + dev_err(&dev->dev, "could not allocate mixer state\n");
The error message is usually superfluous, as kernel spews the warning
in anyway at each allocation error.
thanks,
Takashi
More information about the Alsa-devel
mailing list