#include <linux/config.h>
#include <linux/version.h>

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,5)
#define SND_NEED_USB_SET_INTERFACE
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#define SND_NEED_USB_WRAPPER
#endif
#include <sound/driver.h>
#include <linux/usb.h>
#endif

#ifdef OLD_USB
#define snd_usb_complete_callback(x) __old_ ## x
static void __old_snd_usbmidi_in_urb_complete(struct urb* urb);
static void __old_snd_usbmidi_in_midiman_complete(struct urb* urb);
static void __old_snd_usbmidi_out_urb_complete(struct urb* urb);
#endif

#include "../alsa-kernel/usb/usbmidi.c"

#ifdef OLD_USB
static void __old_snd_usbmidi_in_urb_complete(struct urb* urb)
{
	snd_usbmidi_in_urb_complete(urb, NULL);
}

static void __old_snd_usbmidi_in_midiman_complete(struct urb* urb)
{
	snd_usbmidi_in_midiman_complete(urb, NULL);
}

static void __old_snd_usbmidi_out_urb_complete(struct urb* urb)
{
	snd_usbmidi_out_urb_complete(urb, NULL);
}
#endif

/*
 * workarounds / hacks for the older kernels follow below
 */

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 5, 24)
/* M-Audio Quattro has weird alternate settings.  the altsetting jumps
 * from 0 to 4 or 3 insuccessively, and this screws up
 * usb_set_interface() (at least on 2.4.18/19 and 2.4.21).
 */

/*
 * the following is a stripped version of usb_set_interface() with the fix
 * for insuccessive altsetting numbers.
 */

/* stripped version for isochronos only */
static void hack_usb_set_maxpacket(struct usb_device *dev)
{
	int i, b;

	for (i=0; i<dev->actconfig->bNumInterfaces; i++) {
		struct usb_interface *ifp = dev->actconfig->interface + i;
		struct usb_interface_descriptor *as = ifp->altsetting + ifp->act_altsetting;
		struct usb_endpoint_descriptor *ep = as->endpoint;
		int e;

		for (e=0; e<as->bNumEndpoints; e++) {
			b = ep[e].bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
			if (usb_endpoint_out(ep[e].bEndpointAddress)) {
				if (ep[e].wMaxPacketSize > dev->epmaxpacketout[b])
					dev->epmaxpacketout[b] = ep[e].wMaxPacketSize;
			}
			else {
				if (ep[e].wMaxPacketSize > dev->epmaxpacketin [b])
					dev->epmaxpacketin [b] = ep[e].wMaxPacketSize;
			}
		}
	}
}

/* stripped version */
int snd_hack_usb_set_interface(struct usb_device *dev, int interface, int alternate)
{
	struct usb_interface *iface;
	struct usb_interface_descriptor *iface_as;
	int i, ret;

	iface = usb_ifnum_to_if(dev, interface);
	if (!iface)
		return -EINVAL;
	if (iface->num_altsetting == 1)
		return 0;

	for (i = 0; i < iface->num_altsetting; ++i)
		if (get_iface_desc(&iface->altsetting[i])->bAlternateSetting == alternate)
			break;
	if (i >= iface->num_altsetting)
		return -EINVAL;
	iface_as = &iface->altsetting[i];

	if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
				   USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE,
				   alternate,
				   interface, NULL, 0, HZ * 5)) < 0)
		return ret;

	iface->act_altsetting = i;
	for (i = 0; i < iface_as->bNumEndpoints; i++) {
		u8 ep = iface_as->endpoint[i].bEndpointAddress;
		usb_settoggle(dev, ep&USB_ENDPOINT_NUMBER_MASK, usb_endpoint_out(ep), 0);
	}
	hack_usb_set_maxpacket(dev);
	return 0;
}

EXPORT_SYMBOL(snd_hack_usb_set_interface);

#elif LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 5)

/* map altsetting number to index */
int snd_hack_usb_set_interface(struct usb_device *dev, int interface, int alternate)
{
	struct usb_interface *iface;
	unsigned int i;

	iface = usb_ifnum_to_if(dev, interface);
	if (!iface)
		return -EINVAL;
	for (i = 0; i < iface->num_altsetting; ++i) {
		if (get_iface_desc(&iface->altsetting[i])->bAlternateSetting == alternate)
			return real_usb_set_interface(dev, interface, i);
	}
	return -EINVAL;
}

EXPORT_SYMBOL(snd_hack_usb_set_interface);
#endif /* LINUX_VERSION < 2.6.5 */
