Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 96f61d9a authored by Clemens Ladisch's avatar Clemens Ladisch Committed by Takashi Iwai
Browse files

sound: usb-audio: allow switching altsetting on Roland USB MIDI devices



Add a mixer control to select between the two altsettings on Roland USB
MIDI devices where the input endpoint is either bulk or interrupt.

Signed-off-by: default avatarClemens Ladisch <clemens@ladisch.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 88cdca9c
Loading
Loading
Loading
Loading
+106 −1
Original line number Diff line number Diff line
/*
 * usbmidi.c - ALSA USB MIDI driver
 *
 * Copyright (c) 2002-2007 Clemens Ladisch
 * Copyright (c) 2002-2009 Clemens Ladisch
 * All rights reserved.
 *
 * Based on the OSS usb-midi driver by NAGANO Daisuke,
@@ -47,6 +47,7 @@
#include <linux/usb.h>
#include <linux/wait.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/rawmidi.h>
#include <sound/asequencer.h>
#include "usbaudio.h"
@@ -109,13 +110,17 @@ struct snd_usb_midi {
	struct list_head list;
	struct timer_list error_timer;
	spinlock_t disc_lock;
	struct mutex mutex;

	struct snd_usb_midi_endpoint {
		struct snd_usb_midi_out_endpoint *out;
		struct snd_usb_midi_in_endpoint *in;
	} endpoints[MIDI_MAX_ENDPOINTS];
	unsigned long input_triggered;
	unsigned int opened;
	unsigned char disconnected;

	struct snd_kcontrol *roland_load_ctl;
};

struct snd_usb_midi_out_endpoint {
@@ -879,6 +884,50 @@ static struct usb_protocol_ops snd_usbmidi_emagic_ops = {
};


static void update_roland_altsetting(struct snd_usb_midi* umidi)
{
	struct usb_interface *intf;
	struct usb_host_interface *hostif;
	struct usb_interface_descriptor *intfd;
	int is_light_load;

	intf = umidi->iface;
	is_light_load = intf->cur_altsetting != intf->altsetting;
	if (umidi->roland_load_ctl->private_value == is_light_load)
		return;
	hostif = &intf->altsetting[umidi->roland_load_ctl->private_value];
	intfd = get_iface_desc(hostif);
	snd_usbmidi_input_stop(&umidi->list);
	usb_set_interface(umidi->chip->dev, intfd->bInterfaceNumber,
			  intfd->bAlternateSetting);
	snd_usbmidi_input_start(&umidi->list);
}

static void substream_open(struct snd_rawmidi_substream *substream, int open)
{
	struct snd_usb_midi* umidi = substream->rmidi->private_data;
	struct snd_kcontrol *ctl;

	mutex_lock(&umidi->mutex);
	if (open) {
		if (umidi->opened++ == 0 && umidi->roland_load_ctl) {
			ctl = umidi->roland_load_ctl;
			ctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
			snd_ctl_notify(umidi->chip->card,
				       SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
			update_roland_altsetting(umidi);
		}
	} else {
		if (--umidi->opened == 0 && umidi->roland_load_ctl) {
			ctl = umidi->roland_load_ctl;
			ctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
			snd_ctl_notify(umidi->chip->card,
				       SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
		}
	}
	mutex_unlock(&umidi->mutex);
}

static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream)
{
	struct snd_usb_midi* umidi = substream->rmidi->private_data;
@@ -898,11 +947,13 @@ static int snd_usbmidi_output_open(struct snd_rawmidi_substream *substream)
	}
	substream->runtime->private_data = port;
	port->state = STATE_UNKNOWN;
	substream_open(substream, 1);
	return 0;
}

static int snd_usbmidi_output_close(struct snd_rawmidi_substream *substream)
{
	substream_open(substream, 0);
	return 0;
}

@@ -954,11 +1005,13 @@ static void snd_usbmidi_output_drain(struct snd_rawmidi_substream *substream)

static int snd_usbmidi_input_open(struct snd_rawmidi_substream *substream)
{
	substream_open(substream, 1);
	return 0;
}

static int snd_usbmidi_input_close(struct snd_rawmidi_substream *substream)
{
	substream_open(substream, 0);
	return 0;
}

@@ -1163,6 +1216,7 @@ static void snd_usbmidi_free(struct snd_usb_midi* umidi)
		if (ep->in)
			snd_usbmidi_in_endpoint_delete(ep->in);
	}
	mutex_destroy(&umidi->mutex);
	kfree(umidi);
}

@@ -1524,6 +1578,52 @@ static int snd_usbmidi_get_ms_info(struct snd_usb_midi* umidi,
	return 0;
}

static int roland_load_info(struct snd_kcontrol *kcontrol,
			    struct snd_ctl_elem_info *info)
{
	static const char *const names[] = { "High Load", "Light Load" };

	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	info->count = 1;
	info->value.enumerated.items = 2;
	if (info->value.enumerated.item > 1)
		info->value.enumerated.item = 1;
	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
	return 0;
}

static int roland_load_get(struct snd_kcontrol *kcontrol,
			   struct snd_ctl_elem_value *value)
{
	value->value.enumerated.item[0] = kcontrol->private_value;
	return 0;
}

static int roland_load_put(struct snd_kcontrol *kcontrol,
			   struct snd_ctl_elem_value *value)
{
	struct snd_usb_midi* umidi = kcontrol->private_data;
	int changed;

	if (value->value.enumerated.item[0] > 1)
		return -EINVAL;
	mutex_lock(&umidi->mutex);
	changed = value->value.enumerated.item[0] != kcontrol->private_value;
	if (changed)
		kcontrol->private_value = value->value.enumerated.item[0];
	mutex_unlock(&umidi->mutex);
	return changed;
}

static struct snd_kcontrol_new roland_load_ctl = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
	.name = "MIDI Input Mode",
	.info = roland_load_info,
	.get = roland_load_get,
	.put = roland_load_put,
	.private_value = 1,
};

/*
 * On Roland devices, use the second alternate setting to be able to use
 * the interrupt input endpoint.
@@ -1549,6 +1649,10 @@ static void snd_usbmidi_switch_roland_altsetting(struct snd_usb_midi* umidi)
		    intfd->bAlternateSetting);
	usb_set_interface(umidi->chip->dev, intfd->bInterfaceNumber,
			  intfd->bAlternateSetting);

	umidi->roland_load_ctl = snd_ctl_new1(&roland_load_ctl, umidi);
	if (snd_ctl_add(umidi->chip->card, umidi->roland_load_ctl) < 0)
		umidi->roland_load_ctl = NULL;
}

/*
@@ -1834,6 +1938,7 @@ int snd_usb_create_midi_interface(struct snd_usb_audio* chip,
	umidi->usb_protocol_ops = &snd_usbmidi_standard_ops;
	init_timer(&umidi->error_timer);
	spin_lock_init(&umidi->disc_lock);
	mutex_init(&umidi->mutex);
	umidi->error_timer.function = snd_usbmidi_error_timer;
	umidi->error_timer.data = (unsigned long)umidi;