[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