/*
 * Purpose: Top level USB audio class initialization and mixer functions
 */

/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#define KBUILD_MODNAME oss_usb
#include "oss_config.h"
#include "ossusb.h"


static const udi_usb_devinfo known_devices[] = {
  {0x763, 0x2002, "M Audio USB AudioSport Duo",
   {
    {"OFF 24bit_hispeed 16bit_hispeed 24bit_lowspeed 16bit_lowspeed", 2,
     0x0014},
    {"OFF 24bit_hispeed 16bit_hispeed 24bit_lowspeed 16bit_lowspeed", 2,
     0x0014}
    }
   },
  {0x763, 0x2001, "M Audio USB AudioSport Quatro"},
  {0x763, 0x2805, "M Audio Sonica"},
  {0x763, 0x2007, "M Audio Sonica Theatre",
   {
    {"OFF 24bit_hispeed 16bit_hispeed 16bit_hispeed 24bit_lowspeed 24bit24bit 16bit 16bit OFF OFF", 3},
    {"OFF 24bit_hispeed 16bit_hispeed 24bit 16bit_lowspeed", 2}
    }
   },
  {0x763, 0x200d, "M Audio OmniStudio USB"},
  {0x41e, 0x3000, "Creative Sound Blaster Extigy"},
  {0x41e, 0x3010, "Creative Sound Blaster MP3+"},
  {0x41e, 0x3020, "Creative Audigy2 NX USB",
   {
    {"OFF 16bit22kHz 16bit44kHz "	/* 0-2 */
     "24bit44kHz 16bit48kHz 24bit48kHz "	/* 3-5 */
     "16bit96kHz 24bit96kHz 16bit22kHz "	/* 6-8 */
     "16bit48kHz 24bit48kHz 16bit5.1ch22kHz "	/* 9-11 */
     "16bit5.1ch48kHz 24bit5.1ch48kHz 16bit7.1ch22kHz "	/* 12-14 */
     "16bit7.1ch48kHz" /* 15 */ , 4},

    {"OFF 16bit32kHz 24bit32kHz "	/* 0-2 */
     "16bit44kHz 24bit44kHz 16bit48kHz "	/* 3-5 */
     "24bit48kHzin 24bit96kHz 24bit96kHz ",	/* 6-8 */
     5}
    }
   },
  {0x46d, 0x8b2, "Logitec Quickcam Pro 4000 (mic)"},
  {0x46d, 0xa01, "Logitec USB Headset"},
  {0x0471, 0x0311, "Philips ToUcam Pro (mic)"},
  {0x672, 0x1041, "Labtec LCS1040 Speaker System"},
  {0x6f8, 0xc000, "Hercules Gamesurround MUSE Pocket"},
  {0x0d8c, 0x000c, "C-Media USB audio"},
  {0x0d8c, 0x0102, "C-Media 2/4/6/8ch USB audio",
   {
    {"OFF 7.1 2.0 3.1 5.1 digital", 2},
    {"OFF stereo"}
    }
   },
  {0x0d8c, 0x0103, "C-Media USB audio"},
  {-1, -1, "USB sound device"}
};

typedef struct
{
  unsigned int devid;
  special_driver_t driver;
}
special_table_t;

extern int usb_mixerstyle;

#define MAX_DEVC	10
static ossusb_devc *devc_list[MAX_DEVC];
static int ndevs = 0;
int usb_quiet = 0;


void
ossusb_dump_desc (unsigned char *desc, int cfg_len)
{
  int i;

  for (i = 0; i < cfg_len; i++)
    {
      if (!(i % 16))
	cmn_err (CE_CONT, "\n%04x: ", i);
      cmn_err (CE_CONT, "%02x ", desc[i]);
    }
  cmn_err (CE_CONT, "\n");
}

unsigned int
ossusb_get_int (unsigned char *p, int nbytes)
{
  unsigned int v = 0;
  int i;

  for (i = 0; i < nbytes; i++)
    {
      v |= (*p++) << (i * 8);
    }

  return v;
}

/*
 * Mixer stuff
 */

static int
decode_vol (usb_control_t * c, unsigned char *buf, int l)
{
  int value, range;

  if (l == 1)
    value = (signed char) buf[0];
  else
    value = (signed short) (buf[0] | (buf[1] << 8));

  range = c->max - c->min;

  value -= c->min;
  value = (value * c->scale + range / 2) / range;

  if (value < 0)
    value = 0;
  if (value > 255)
    value = 255;
  return value;
}

static void
encode_vol (usb_control_t * c, unsigned char *buf, int l, int value)
{
  int range;

  range = c->max - c->min;

  value = (value * range + c->scale / 2) / c->scale;

  value += c->min;

  if (l == 1)
    buf[0] = value;
  else
    {
      buf[0] = value & 0xff;
      buf[1] = (value >> 8) & 0xff;
    }

}

static int
read_feature_value (ossusb_devc * devc, usb_control_t * c, int rq, int *v)
{
  int len, value = 0, l;
  int nch, ch;
  unsigned char buf[2];

/*
 * Volume controls use two message bytes while the others use just one.
 */
  l = (c->index == 1) ? 2 : 1;

  ch = c->min_ch + 1;
  if (c->global)
    ch = 0;

  if (rq != GET_CUR)
    {
      buf[0] = buf[1] = 0;
      len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
				     rq, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				     ((c->index + 1) << 8) | (ch),	// value
				     (c->unit->num << 8),	// index
				     buf,	// buffer
				     l,	// buflen
				     OSS_HZ * 2);
      if (len < 0)
	return 0;

      value =
	(len ==
	 1) ? (signed char) buf[0] : (signed short) (buf[0] | (buf[1] << 8));
      *v = value;
      return 1;
    }

  nch = c->max_ch - c->min_ch + 1;

  buf[0] = buf[1] = 0;
  len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
				 GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				 ((c->index + 1) << 8) | (ch),	// value
				 (c->unit->num << 8),	// index
				 buf,	// buffer
				 l,	// buflen
				 OSS_HZ * 2);
  if (len < 0)
    {
      // cmn_err(CE_CONT, "feature (%s/%d) read error %d\n", c->unit->name, c->index, len);
      *v = 0;
      return 0;
    }

  value = decode_vol (c, buf, len);

  if (!c->global)
    if (nch == 2)		/* Read the right channel */
      {
	len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
				       GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				       ((c->index + 1) << 8) | (ch + 1),	// value
				       (c->unit->num << 8),	// index
				       buf,	// buffer
				       l,	// buflen
				       OSS_HZ * 2);
	if (len < 0)
	  value |= (value & 0xff) << 8;
	else
	  value |= (decode_vol (c, buf, len)) << 8;
      }

  *v = value;
  return 1;
}

static int
write_feature_value (ossusb_devc * devc, usb_control_t * c, int value)
{
  int len, l;
  int left, right;
  int ch, nch;
  unsigned char buf[2];

  ch = c->min_ch + 1;
  if (c->global)
    ch = 0;

  left = value & 0xff;
  right = (value >> 8) & 0xff;

  if (left > (c->max - c->min))
    left = c->max - c->min;
  if (right > (c->max - c->min))
    right = c->max - c->min;

  l = (c->index == 1) ? 2 : 1;

  nch = c->max_ch - c->min_ch + 1;
  buf[0] = buf[1] = 0;
  encode_vol (c, buf, l, left);

  len = udi_usb_snd_control_msg (devc->mixer_usbdev, 0,	// endpoint
				 SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				 ((c->index + 1) << 8) | ch,	// value
				 (c->unit->num << 8),	// index
				 buf,	// buffer
				 l,	// buflen
				 OSS_HZ * 2);
  if (len < 0)
    {
      cmn_err (CE_CONT, "feature write error %d\n", len);
      return len;
    }

  if (nch == 2)			/* Write the right channel */
    {
      buf[0] = buf[1] = 0;
      encode_vol (c, buf, l, right);

      len = udi_usb_snd_control_msg (devc->mixer_usbdev, 0,	// endpoint
				     SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				     ((c->index + 1) << 8) | (ch + 1),	// value
				     (c->unit->num << 8),	// index
				     buf,	// buffer
				     l,	// buflen
				     OSS_HZ * 2);
    }
  else
    right = left;

  value = left | (right << 8);
  return value;
}

static int
read_mixer_value (ossusb_devc * devc, usb_control_t * c, int rq, int *v)
{
  int len, value = 0;
  unsigned char buf[2];

  if (rq != GET_CUR)
    {
      len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
				     rq, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				     (c->index << 8) | c->min_ch,	// value
				     (c->unit->num << 8) | 1,	// index
				     buf,	// buffer
				     2,	// buflen
				     OSS_HZ * 2);
      if (len < 0)
	return 0;

      *v = (signed char) buf[1];
      return 1;
    }

  len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
				 rq, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				 (c->index << 8) | c->min_ch,	// value
				 (c->unit->num << 8) | 1,	// index
				 buf,	// buffer
				 2,	// buflen
				 OSS_HZ * 2);
  if (len < 0)
    {
      cmn_err (CE_CONT, "mixer read error %d\n", len);
      return 0;
    }

  value = buf[1] - c->min;

  *v = value;
  return 1;
}

static int
write_mixer_value (ossusb_devc * devc, usb_control_t * c, int value)
{
  int len;
  unsigned char buf[2];

  value &= 0xff;

  buf[0] = 0;
  buf[1] = value + c->min;
  len = udi_usb_snd_control_msg (devc->mixer_usbdev, 0,	// endpoint
				 SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				 (c->index << 8) | c->min_ch,	// value
				 (c->unit->num << 8) | 1,	// index
				 buf,	// buffer
				 2,	// buflen
				 OSS_HZ * 2);
  if (len < 0)
    {
      cmn_err (CE_CONT, "mixer write error %d\n", len);
      return 0;
    }

  return value;
}

static int
read_unit (ossusb_devc * devc, usb_control_t * c, int rq, int *v)
{
  int err, value;
  unsigned char buf[2];

  *v = value = 0;

  switch (c->unit->typ)
    {
    case TY_FEAT:
      return read_feature_value (devc, c, rq, v);
      break;

    case TY_MIXER:
      return read_mixer_value (devc, c, rq, v);
      break;

    case TY_SELECT:
      err = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
				     rq, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				     0,	// value
				     (c->unit->num << 8),	// index
				     buf,	// buffer
				     1,	// buflen
				     OSS_HZ * 2);
      if (err < 0)
	{
	  cmn_err (CE_CONT, "USB mixer unit read error %d\n", err);
	  return 0;
	}
      value = buf[0] - 1;
      break;

    }				/* switch */

  *v = value;
  return 1;
}

static int
read_current_value (ossusb_devc * devc, usb_control_t * c)
{
  int v;

  if (!read_unit (devc, c, GET_CUR, &v))
    return 0;

  c->value = v;

  return 1;
}

static int
write_current_value (ossusb_devc * devc, usb_control_t * c, int value)
{
  unsigned char buf[2];
  int err;

  switch (c->unit->typ)
    {
    case TY_SELECT:
      if (value > c->max)
	value = c->max;
      buf[0] = value + 1;
      err = udi_usb_snd_control_msg (devc->mixer_usbdev, 0,	// endpoint
				     SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
				     0,	// value
				     (c->unit->num << 8),	// index
				     buf,	// buffer
				     1,	// buflen
				     OSS_HZ * 2);
      if (err < 0)
	{
	  cmn_err (CE_CONT, "Selector write error %d\n", err);
	  return 0;
	}
      break;

    case TY_FEAT:
      value = write_feature_value (devc, c, value);
      break;

    case TY_MIXER:
      value = write_mixer_value (devc, c, value);
      break;

    default:
      return OSS_EIO;
    }

  return value;
}

static int
new_ctl (ossusb_devc * devc, usb_audio_unit_t * un, int index, int exttype,
	 int chmask)
{
  int n, min, max;
  usb_control_t *c;
  int i, min_ch = 0, max_ch = 0;

  if (devc->ncontrols >= MAX_CONTROLS)
    {
      cmn_err (CE_CONT, "Too many mixer features.\n");
      return OSS_EIO;
    }

  n = devc->ncontrols++;

  c = &devc->controls[n];

  if (chmask)
    {
      min_ch = -1;

      for (i = 0; i < 32; i++)
	if (chmask & (1 << i))
	  {
	    if (min_ch == -1)
	      min_ch = i;
	    max_ch = i;
	  }
    }

  c->unit = un;
  c->index = index;
  c->exttype = exttype;
  c->min_ch = min_ch;
  c->global = (chmask == 0) ? 1 : 0;
  c->max_ch = max_ch;
  c->min = 0;
  c->max = 255;
  c->chmask = chmask;
  c->value = 0;

  if (index != 1)		/* Not volume control */
    {
      c->max = 1;
    }
  else
    {
      if (read_unit (devc, c, GET_MAX, &max))
	{
	  c->max = max;
	  if (read_unit (devc, c, GET_MIN, &min))
	    {
	      c->min = min;
	    }
	}
    }

  if (c->max <= c->min)
    {
/*
 * The device reported bad limits. Try to fix the situation.
 */
      if (c->min < 0)
	c->max = 0;
      else
	c->min = 0;
      if (c->max <= c->min)
	{
	  c->min = 0;
	  c->max = 255;
	}
    }

  c->scale = 255;
  if (c->max - c->min < 255)
    c->scale = c->max - c->min;


  read_current_value (devc, c);

  if (usb_trace)
    {
      cmn_err (CE_CONT, "Ctl %2d: %d/%s %d ", n, c->unit->num, c->unit->name,
	       c->index);
      cmn_err (CE_CONT, "Min %d, max %d, scale %d\n", c->min, c->max,
	       c->scale);
      cmn_err (CE_CONT, "ch=%x ", c->chmask);
      cmn_err (CE_CONT, "Value %04x, (%d - %d, %d)\n", c->value, c->min,
	       c->max, c->scale);
    }

  return n;
}

static int
mixer_func (int dev, int ctrl, unsigned int cmd, int value)
{
  ossusb_devc *devc = mixer_devs[dev]->devc;
  usb_control_t *c;

  if (devc->disabled)
    return OSS_EPIPE;

  if (ctrl < 0 || ctrl >= devc->ncontrols)
    return OSS_EIO;

  c = &devc->controls[ctrl];

  if (cmd == SNDCTL_MIX_READ)
    {
      return c->value;
    }

  if (cmd != SNDCTL_MIX_WRITE)
    return OSS_EINVAL;

  value = c->value = write_current_value (devc, c, value);

  return value;
}

static char *
get_feature_name (int n)
{
  switch (n)
    {
    case 0:
      return "mute";
    case 1:
      return "vol";
    case 2:
      return "bass";
    case 3:
      return "mid";
    case 4:
      return "treble";
    case 5:
      return "eq";
    case 6:
      return "agc";
    case 7:
      return "delay";
    case 8:
      return "boost";
    case 9:
      return "loud";
    case 10:
      return "igain";
    case 11:
      return "igainpad";
    case 12:
      return "phaseinv";
    case 13:
      return "underflow";
    case 14:
      return "overflow";
    default:
      return "misc";
    }
}

static int
get_feature_type (int n)
{
  switch (n)
    {
    case 0:
      return MIXT_ONOFF;
    case 1:
      return MIXT_SLIDER;
    case 2:
      return MIXT_SLIDER;
    case 3:
      return MIXT_SLIDER;
    case 4:
      return MIXT_SLIDER;
    case 5:
      return MIXT_SLIDER;
    case 6:
      return MIXT_ONOFF;
    case 7:
      return MIXT_SLIDER;
    case 8:
      return MIXT_ONOFF;
    case 9:
      return MIXT_ONOFF;
    default:
      return MIXT_ONOFF;
    }
}

struct chmasks
{
  unsigned int mask;
  char *name;
  char type;
  char channels;
};

static const struct chmasks chmasks[] = {
  {0x00000003, "front", MIXT_STEREOSLIDER, 2},
  {0x00000001, "L", MIXT_MONOSLIDER, 1},
  {0x00000002, "R", MIXT_MONOSLIDER, 1},
  {0x00000030, "surr", MIXT_STEREOSLIDER, 2},
  {0x00000010, "LS", MIXT_MONOSLIDER, 1},
  {0x00000020, "RS", MIXT_MONOSLIDER, 1},
  {0x0000000c, "C/L", MIXT_STEREOSLIDER, 2},
  {0x00000004, "C", MIXT_MONOSLIDER, 1},
  {0x00000008, "LFE", MIXT_MONOSLIDER, 1},
  {0x000000c0, "center", MIXT_STEREOSLIDER, 2},
  {0x00000040, "LC", MIXT_MONOSLIDER, 1},
  {0x00000080, "RC", MIXT_MONOSLIDER, 1},
  {0x00000100, "surr", MIXT_STEREOSLIDER, 2},
  {0x00000600, "side", MIXT_STEREOSLIDER, 2},
  {0x00000200, "SL", MIXT_MONOSLIDER, 1},
  {0x00000400, "SR", MIXT_MONOSLIDER, 1},
  {0x00000800, "TC", MIXT_MONOSLIDER, 1},
  {0x00001000, "TFL", MIXT_MONOSLIDER, 1},
  {0x00002000, "TFC", MIXT_MONOSLIDER, 1},
  {0x00004000, "TFR", MIXT_MONOSLIDER, 1},
  {0x00008000, "TBL", MIXT_MONOSLIDER, 1},
  {0x00010000, "TBC", MIXT_MONOSLIDER, 1},
  {0x00020000, "TBR", MIXT_MONOSLIDER, 1},
  {0x00040000, "TFLC", MIXT_MONOSLIDER, 1},
  {0x00080000, "TFRC", MIXT_MONOSLIDER, 1},
  {0x00100000, "LLFE", MIXT_MONOSLIDER, 1},
  {0x00200000, "RLFE", MIXT_MONOSLIDER, 1},
  {0x00400000, "TSL", MIXT_MONOSLIDER, 1},
  {0x00800000, "TSR", MIXT_MONOSLIDER, 1},
  {0x01000000, "BC", MIXT_MONOSLIDER, 1},
  {0x02000000, "BLC", MIXT_MONOSLIDER, 1},
  {0x04000000, "BRC", MIXT_MONOSLIDER, 1},
  {0x80000000, "RD", MIXT_MONOSLIDER, 1},
  {0, NULL}
};

static int
count_source_controls (ossusb_devc * devc, int unit)
{
  int n = 0, nn, i;
  usb_audio_unit_t *un;
  unsigned char *d;

  if (unit <= 0 || unit >= devc->nunits)
    return 0;

  un = &devc->units[unit];
  d = un->desc;

  if (un == NULL)
    return 0;

  switch (un->typ)
    {
    case TY_MIXER:
    case TY_SELECT:
      nn = d[4];
      d += 5;
      for (i = 0; i < nn; i++)
	{
	  n += count_source_controls (devc, *d);
	  d++;
	}
      break;

    case TY_PROC:
    case TY_EXT:
      nn = d[6];
      d += 7;
      for (i = 0; i < nn; i++)
	{
	  n += count_source_controls (devc, *d);
	  d++;
	}
      break;

    default:
      if (un->source <= 0 && un->source < devc->nunits)
	{
	  n = count_source_controls (devc, un->source);
	}
    }

  if (!un->ctl_avail)
    return n;

  return n + un->control_count;
}

static int
count_target_controls (ossusb_devc * devc, int unit)
{
  int n = 0;
  usb_audio_unit_t *un;

  if (unit <= 0 || unit >= devc->nunits)
    return 0;

  un = &devc->units[unit];

  if (un == NULL)
    return 0;

  if (un->typ == TY_SELECT || un->typ == TY_MIXER)
    {
      n = 0;
    }
  else if (un->target <= 0 && un->target < devc->nunits)
    {
      n = count_target_controls (devc, un->target);
    }

  if (!un->ctl_avail)
    return n;

  return n + un->control_count;
}

static int
follow_source_links (ossusb_devc * devc, usb_audio_unit_t * un)
{
  while (un->source > 0 && un->source < devc->nunits)
    un = &devc->units[un->source];

  return un->num;
}

static int
follow_target_links (ossusb_devc * devc, usb_audio_unit_t * un)
{
  while (un->target > 0 && un->target < devc->nunits)
    un = &devc->units[un->target];

  return un->num;
}

static void
add_controls_for_mixer (ossusb_devc * devc, usb_audio_unit_t * un, int group)
{
  int i, n;
  unsigned char *d = un->desc;

  if (!un->ctl_avail)
    return;

  n = d[4];
  d += 5;

  for (i = 0; i < n; i++)
    if (*d > 0 && *d < devc->nunits)
      {
	int ref = follow_source_links (devc, &devc->units[*d]);

	{

	  int ctl = new_ctl (devc, un, i, MIXT_MONOSLIDER, un->chmask);
	  UDB (cmn_err
	       (CE_CONT, "Add mixer control %d/%s\n", un->num,
		devc->units[ref].name));
	  if (mixer_ext_create_control (devc->mixer_dev, group, ctl,
					mixer_func, MIXT_MONOSLIDER,
					devc->units[ref].name, 255,
					MIXF_READABLE | MIXF_WRITEABLE) < 0)
	    return;
	}
	d++;
      }

  un->ctl_avail = 0;
}

/*ARGSUSED*/
static void
add_controls_for_proc (ossusb_devc * devc, usb_audio_unit_t * un, int group)
{
  int i, n;
  unsigned char *d = un->desc;

  if (!un->ctl_avail)
    return;

  n = d[6];
  d += 7;

  for (i = 0; i < n; i++)
    if (*d > 0 && *d < devc->nunits)
      {
	d++;
      }

  un->ctl_avail = 0;
}

static void
add_controls_for_selector (ossusb_devc * devc, usb_audio_unit_t * un,
			   int group)
{
  static oss_mixer_enuminfo ent;
  char *s;
  int i, n, err;
  unsigned char *d = un->desc;
  int ctl = new_ctl (devc, un, 0, MIXT_ENUM, 0);

  if (!un->ctl_avail)
    return;

  n = d[4];
  d += 5;
  UDB (cmn_err (CE_CONT, "Add selector control %d/%s\n", un->num, un->name));
  if ((err = mixer_ext_create_control (devc->mixer_dev, group,
				       ctl, mixer_func,
				       MIXT_ENUM,
				       un->name, n,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return;

  memset (&ent, 0, sizeof (ent));

  s = ent.strings;

  for (i = 0; i < n; i++)
    if (*d > 0 && *d < devc->nunits)
      {
	int ref = follow_source_links (devc, &devc->units[*d]);
	d++;

	if (s > ent.strings)
	  *s++ = '|';
	strcpy (s, devc->units[ref].name);
	s += strlen (s);
      }

  s = ent.strings;
  for (i = 0; i < strlen (s); i++)
    {
      if (s[i] == ' ')
	s[i] = '_';
      if (s[i] == '|')
	s[i] = ' ';
    }

  ent.dev = devc->mixer_dev;
  ent.ctrl = err;
  ent.nvalues = 0;

  mixer_ext_set_enum (&ent);

  un->ctl_avail = 0;
}

/*ARGSUSED*/
static void
add_multich_volumes (ossusb_devc * devc, usb_audio_unit_t * un, int group,
		     int fea, int mask, unsigned int *feature_mask)
{
  int i;

  for (i = 0; mask != 0 && chmasks[i].mask != 0; i++)
    {
      int m = chmasks[i].mask;
      int ctl;
      usb_control_t *c;

      if (!(mask & m))
	continue;

      ctl = new_ctl (devc, un, fea, chmasks[i].type, chmasks[i].mask);
      c = &devc->controls[ctl];

      UDB (cmn_err
	   (CE_CONT, "Add multich feature control %d/%s\n", un->num,
	    chmasks[i].name));
      if (mixer_ext_create_control (devc->mixer_dev, group, ctl, mixer_func,
				    chmasks[i].type, chmasks[i].name,
				    c->scale,
				    MIXF_READABLE | MIXF_WRITEABLE) < 0)
	return;

      mask &= ~m;
    }

  if (mask)
    cmn_err (CE_CONT, "Warning! Unsupported channels (%02x)\n", mask);
}

static void
translate_feature_mask_usb2 (ossusb_devc *devc, usb_audio_unit_t * un, unsigned int *feature_mask)
{
  int i, n, c;
  unsigned char *d = un->desc;

  n = 4;
  d = d + 5 + n;		// Skip the global mask

/*
 * USB 2.0 uses 2 bits for each control
 * 	01b means that the control is read only and 11b means it's RW
 */
  for (c = 0; c < un->channels; c++)
    {
      unsigned int mask;
      unsigned char *p = d + c * n;

      mask = p[3] |
	     (p[2] << 8) |
	     (p[1] << 16) |
	     (p[0] << 24);

      for (i = 0; i < n * 4; i++)
	if ((mask & (3 << 2*i)) && (un->ctl_avail & (1 << i)))
	  {
	    feature_mask[i] |= 1 << c;
	  }
    }
}

static void
translate_feature_mask (ossusb_devc *devc, usb_audio_unit_t * un, unsigned int *feature_mask)
{
  int i, n, c;
  unsigned char *d = un->desc;

  if (devc->usb_version > 1)
  {
	translate_feature_mask_usb2(devc, un, feature_mask);
	return;
  }

  n = d[5];
  if (n < 1 || n > 2)
    {
      	cmn_err (CE_CONT, "Bad feature mask size %d\n", n);
       	return;
    }

    d = d + 6 + n;		// Skip the global mask

  for (c = 0; c < un->channels; c++)
    {
      int mask = d[c * n];
      if (n > 1)
	mask |= d[c * n + 1];

      for (i = 0; i < n * 8; i++)
	if ((mask & (1 << i)) && (un->ctl_avail & (1 << i)))
	  {
	    feature_mask[i] |= 1 << c;
	  }
    }
}

static void
add_controls_for_feature (ossusb_devc * devc, usb_audio_unit_t * un,
			  int group)
{
  int i;
  unsigned int feature_mask[16], global_mask;

  if (!un->ctl_avail)
    return;

// Handle the global controls first

  global_mask = un->desc[6];
  if (un->desc[5] > 0)
    global_mask |= un->desc[7] << 8;

  for (i = 0; i < 11; i++)
    if (un->ctl_avail & (1 << i) && (global_mask & (1 << i)))
      {
	char *name = get_feature_name (i);
	int type = get_feature_type (i);
	int max;
	char tmp[16];
	int ctl;

	ctl = new_ctl (devc, un, i, type, 0);
	strcpy (tmp, name);
	if (type == MIXT_SLIDER)
	  max = devc->controls[ctl].max - devc->controls[ctl].min;
	else
	  max = 1;

	if (max > 255)
	  max = 255;

	UDB (cmn_err
	     (CE_CONT, "Add (global) feature control %d:%s, max=%d\n",
	      un->num, name, max));
	if (mixer_ext_create_control (devc->mixer_dev, group, ctl,
				      mixer_func, type, tmp, max,
				      MIXF_READABLE | MIXF_WRITEABLE) < 0)
	  return;
	//un->ctl_avail &= ~(1 << i);
      }

// Handle the channelized controls

  if (un->ctl_avail == 0)	/* Nothing left */
    return;

  // Translate the channel/feature availability matrix

  memset (feature_mask, 0, sizeof (feature_mask));
  translate_feature_mask (devc, un, feature_mask);

  for (i = 0; i < 16; i++)
    if (feature_mask[i])
      {
	char *name = get_feature_name (i);
	int type = get_feature_type (i);
	int max, instances = 0;

	int nc = 0, j;

	for (j = 0; j < 16; j++)
	  if (feature_mask[i] & (1 << j))
	    nc = j + 1;

	if (type == MIXT_SLIDER)
	  {
	    if (nc == 1)
	      {
		type = MIXT_MONOSLIDER;
		instances = 1;
	      }
	    else if (nc == 2)
	      {
		type = MIXT_STEREOSLIDER;
		instances = 1;
	      }
	    else
	      {
		type = MIXT_MONOSLIDER;
		if (i == 1)	/* "volume" */
		  instances = nc;
		else
		  {
		    instances = 0;
		    type = MIXT_MONOSLIDER;
		  }
	      }
	    max = 255;
	  }
	else
	  max = 2;

	if (instances && (instances > 1 || feature_mask[i] > 0x0003))
	  {
	    int g;
	    char tmp[16];
	    strcpy (tmp, name);
	    UDB (cmn_err (CE_CONT, "Add feature group %s\n", tmp));
	    if ((g =
		 mixer_ext_create_group (devc->mixer_dev, group, tmp)) < 0)
	      return;

	    add_multich_volumes (devc, un, g, i, feature_mask[i],
				 feature_mask);
	  }
	else
	  {
	    char tmp[16];
	    int ctl = new_ctl (devc, un, i, type, un->chmask);
	    strcpy (tmp, name);
	    max = devc->controls[ctl].max - devc->controls[ctl].min;
	    if (max > 255)
	      max = 255;

	    UDB (cmn_err
		 (CE_CONT, "Add feature control %d:%s\n", un->num, name));
	    if (mixer_ext_create_control (devc->mixer_dev, group, ctl,
					  mixer_func, type, tmp, max,
					  MIXF_READABLE | MIXF_WRITEABLE) < 0)
	      return;
	  }
      }

  un->ctl_avail = 0;		// All done (hope so)
}

static void
traverse_source_controls (ossusb_devc * devc, usb_audio_unit_t * un,
			  int group)
{
  unsigned char *d;

  d = un->desc;

  if (un->typ == TY_MIXER)
    {
      add_controls_for_mixer (devc, un, group);
      return;
    }

  if (un->typ == TY_PROC)
    {
      add_controls_for_proc (devc, un, group);

      if (d[6] > 1)		/* More than 1 sources */
	return;
    }

  if (un->typ == TY_SELECT)
    {
      add_controls_for_selector (devc, un, group);
      return;
    }

  if (un->source > 0 && un->source < devc->nunits)
    {
      traverse_source_controls (devc, &devc->units[un->source], group);
    }

  if (un->typ == TY_FEAT)
    {
      add_controls_for_feature (devc, un, group);
    }
}

static void
traverse_target_controls (ossusb_devc * devc, usb_audio_unit_t * un,
			  int group)
{
  unsigned char *d;


  d = un->desc;

  if (un->typ == TY_SELECT)
    {
      add_controls_for_selector (devc, un, group);
    }

  if (un->typ == TY_PROC || un->typ == TY_EXT)
    if (d[6] > 1)		// More than 1 input pins
      return;

  if (un->typ == TY_MIXER)
    {
#if 1
      add_controls_for_mixer (devc, un, group);
#endif
    }

  if (un->target > 0 && un->target < devc->nunits)
    traverse_target_controls (devc, &devc->units[un->target], group);

  if (un->typ == TY_FEAT)
    {
      add_controls_for_feature (devc, un, group);
    }
}

void
ossusb_create_altset_control (int dev, int portc_num, int nalt, char *name)
{
  int ctl;
  ossusb_devc *devc = mixer_devs[dev]->devc;
  char *as = NULL;
  int default_altsetting;
  unsigned int altsetting_mask;

  if (nalt < 3)			/* Only one alternative setting (plus "OFF") available */
    return;
  if ((as =
       udi_usbdev_get_altsetting_labels (devc->mixer_usbdev, portc_num,
					 &default_altsetting,
					 &altsetting_mask)) != NULL)
    {
      if (altsetting_mask != 0)
	{
	  /*
	   * Check if more than one of them are enabled.
	   */

	  int i, n = 0;

	  for (i = 1; i < nalt; i++)
	    if (altsetting_mask & (1 << i))
	      n++;

	  if (n < 2)		/* No */
	    return;
	}
    }

  if ((ctl = mixer_ext_create_control (dev, 0,
				       portc_num, ossusb_change_altsetting,
				       MIXT_ENUM,
				       name, nalt,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return;

  if (as != NULL)		/* Use custom labels */
    {
      mixer_ext_set_strings (dev, ctl, as, 0);
      if (altsetting_mask != 0)
	{
	  oss_mixext *ext;
	  int i;

	  ext = mixer_find_ext (dev, ctl);

	  memset (ext->enum_present, 0, sizeof (ext->enum_present));
	  for (i = 0; i < nalt; i++)
	    if (altsetting_mask & (1 << i))
	      ext->enum_present[i / 8] |= (1 << (i % 8));
	}

      if (default_altsetting > 0)
	ossusb_change_altsetting (dev, portc_num, SNDCTL_MIX_WRITE,
				  default_altsetting);
    }
  else
    mixer_ext_set_strings (dev, ctl,
			   "OFF 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19",
			   0);

}

static int
usb_mix_init (int dev)
{
  ossusb_devc *devc;
  int i, group;

  devc = mixer_devs[dev]->devc;

  for (i = 0; i < devc->nunits; i++)
    {
      usb_audio_unit_t *un = &devc->units[i];

      if (un->typ == TY_OUTPUT)
	{
	  if (count_source_controls (devc, un->source) == 0)
	    {
	      continue;
	    }

	  if ((group = mixer_ext_create_group (dev, 0, un->name)) < 0)
	    return group;

	  traverse_source_controls (devc, un, group);
	  continue;
	}
    }

  for (i = 0; i < devc->nunits; i++)
    {
      usb_audio_unit_t *un = &devc->units[i];

      if (un->typ == TY_INPUT)
	{
	  if (count_target_controls (devc, un->target) == 0)
	    {
	      continue;
	    }

	  if ((group = mixer_ext_create_group (dev, 0, un->name)) < 0)
	    return group;

	  traverse_target_controls (devc, un, group);
	  un->ctl_avail = 0;
	  continue;
	}
    }

  return 0;
}

static char *
check_feature (usb_audio_unit_t * un, unsigned char *mask, int n)
{
  if (mask[n / 8] & (1 << (n % 8)))
    {
      un->control_count++;

      return get_feature_name (n);
    }

  return NULL;
}

/*ARGSUSED*/
static int
usb_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
{
  ossusb_devc *devc = mixer_devs[dev]->hw_devc;

  if (devc->disabled)
    return OSS_EPIPE;

  switch (cmd)
    {
    case SOUND_MIXER_READ_DEVMASK:
      return *arg = devc->devmask | devc->recmask;
    case SOUND_MIXER_READ_RECMASK:
      return *arg = devc->recmask;
    case SOUND_MIXER_READ_RECSRC:
      return *arg = devc->recsrc;
    case SOUND_MIXER_READ_STEREODEVS:
      return *arg = devc->stereodevs;
    }

  return *arg = 0;
}

static mixer_driver_t usb_mixer_driver = {
  usb_mixer_ioctl
};

static int
init_mixer (ossusb_devc * devc)
{
  if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
					    devc->osdev,
					    devc->osdev,
					    devc->dev_name,
					    &usb_mixer_driver,
					    sizeof (mixer_driver_t),
					    devc)) >= 0)
    {
      char mxname[20];

      sprintf (mxname, "USB_%d", ndevs);

      devc->levels = load_mixer_volumes (mxname, NULL, 1);
      mixer_devs[devc->mixer_dev]->hw_devc = devc;
      mixer_ext_set_init_fn (devc->mixer_dev, usb_mix_init, MAX_CONTROLS);
    }

  return 1;
}

static usb_audio_unit_t *
setup_unit (ossusb_devc * devc, int unit, char *name, unsigned char *desc,
	    int desclen, int typ)
{
  usb_audio_unit_t *un;

  if (unit < 0 || unit >= MAX_UNIT)
    {
      cmn_err (CE_WARN, "Bad unit number %d\n", unit);
      return NULL;
    }

  if (typ >= sizeof (devc->unit_counters))
    {
      cmn_err (CE_WARN, "Bad unit type %d\n", typ);
      return NULL;
    }

  devc->unit_counters[typ]++;

  if (unit >= devc->nunits)
    devc->nunits = unit + 1;

  un = &devc->units[unit];

  un->num = unit;
  un->typ = typ;
  un->desc = desc;
  un->desclen = desclen;
  un->mixnum = -1;
  un->channels = 2;
  un->chmask = 0x03;
  strcpy (un->name, name);

  return un;
}

static char *
get_terminal_id (int type, char *def, int typ, int *mn)
{
  int dummy, *mixnum;

  if (mn != NULL)
    mixnum = mn;
  else
    mixnum = &dummy;
  switch (type)
    {
    case 0x0101:
      if (typ == TY_INPUT)
	{
	  *mixnum = SOUND_MIXER_PCM;
	  return "play";
	}
      if (typ == TY_OUTPUT)
	{
	  *mixnum = SOUND_MIXER_RECLEV;
	  return "rec";
	}
      break;
    case 0x0301:
      *mixnum = SOUND_MIXER_VOLUME;
      return "output";
    case 0x0302:
      *mixnum = SOUND_MIXER_VOLUME;
      return "headph";
    case 0x0307:
      return "LFE";

    case 0x0401:
      return "handset";
    case 0x0402:
      return "headset";

    case 0x0403:
    case 0x0404:
    case 0x0405:
    case 0x0500:
    case 0x0501:
    case 0x0502:
    case 0x0504:
      return "phone";
      break;

    case 0x0600:
      *mixnum = SOUND_MIXER_LINE1;
      return "aux";
    case 0x0601:
      *mixnum = SOUND_MIXER_LINE2;
      return "aux";
    case 0x0602:
      return "digital";
    case 0x0603:
      *mixnum = SOUND_MIXER_LINE;
      return "line";
    case 0x0604:
      *mixnum = SOUND_MIXER_SPEAKER;
      return "pc_in";
    case 0x0605:
      *mixnum = SOUND_MIXER_DIGITAL1;
      if (typ == TY_INPUT)
	return "spdin";
      else
	return "spdout";
    case 0x0606:
      return "da_stream";
    case 0x0607:
      return "DV_audio";

    case 0x0701:
      return "noise";
    case 0x0702:
      return "eq_noise";
    case 0x0703:
      *mixnum = SOUND_MIXER_CD;
      return "cd";
    case 0x0704:
      return "dat";
    case 0x0706:
      return "md";
    case 0x0707:
      return "tape";
    case 0x0708:
      return "phono";
    case 0x0709:
      *mixnum = SOUND_MIXER_VIDEO;
      return "vcr";
    case 0x070b:
      return "dvd";
    case 0x070c:
      return "tv";
    case 0x070d:
      return "sat";
    case 0x070e:
      return "cable";
    case 0x0710:
      *mixnum = SOUND_MIXER_RADIO;
      return "radio";
    case 0x0711:
      *mixnum = SOUND_MIXER_RADIO;
      return "xmitter";
    case 0x0712:
      return "mtrack";
    case 0x0713:
      *mixnum = SOUND_MIXER_SYNTH;
      return "synth";
    }

  if (type < 0x201)		// Unknown type
    return def;

  if (type < 0x300)
    {
      *mixnum = SOUND_MIXER_MIC;
      return "mic";
    }

  return def;
}

static void
setup_legacy_mixer (ossusb_devc * devc)
{
  int x;

// Set up the recording selector

  for (x = 1; x < devc->nunits; x++)
    {
      usb_audio_unit_t *un = &devc->units[x];
      int i, n;
      unsigned char *d = un->desc;

      if (un->typ != TY_SELECT || strcmp (un->name, "rec.src") != 0)
	continue;

      if (!un->ctl_avail)
	continue;

      devc->rec_unit = x;
      un->ctl_avail = 0;

      devc->num_recdevs = n = d[4];
      d += 5;
      for (i = 0; i < n; i++)
	{
	  int ref = follow_source_links (devc, &devc->units[*d]);
	  int mask;

	  d++;

	  if (ref < 1 || ref >= devc->nunits || devc->units[ref].mixnum == -1)
	    continue;

	  mask = (1 << devc->units[ref].mixnum);
	  if (devc->recmask & mask)	// Duplicate
	    continue;
	  UDB (cmn_err
	       (CE_CONT, "Recsrc %d, num=%d, mask %x\n", i, ref, mask));

	  devc->recdevs[i] = mask;

	  devc->recmask |= mask;
	  if (devc->recsrc == 0)
	    devc->recsrc = mask;
	}

      break;
    }

// Set up the legacy mixer controls

  for (x = 1; x < devc->nunits; x++)
    {
      usb_audio_unit_t *un = &devc->units[x];
      int mixnum;
      unsigned char *d = un->desc;

      if (!(un->ctl_avail & 0x02))
	continue;

      if (un->typ == TY_FEAT && d[6] & 0x02)	// Volume control
	{
	  int t;
	  mixnum = un->mixnum;

	  if (mixnum == -1)
	    {
	      t = follow_target_links (devc, un);
	      if (t && devc->units[t].mixnum != -1)
		mixnum = devc->units[t].mixnum;
	      else
		{
		  t = follow_source_links (devc, un);
		  if (t && devc->units[t].mixnum != -1)
		    mixnum = devc->units[t].mixnum;
		}
	    }

	  if (mixnum == -1 || un->channels > 2)
	    continue;
	  UDB (cmn_err (CE_CONT, "Unit %d is volume %d\n", x, mixnum));
	  un->ctl_avail &= ~0x02;	// Reserve the volume slider

	  devc->devmask |= (1 << mixnum);
	  if (un->channels == 2)
	    devc->stereodevs |= (1 << mixnum);

	  continue;
	}
    }

}

static char *
get_processor_type (int t)
{
  switch (t)
    {
    case 0x01:
      return "upmix";
    case 0x02:
      return "prologic";
    case 0x03:
      return "3D";
    case 0x04:
      return "reverb";
    case 0x05:
      return "chorus";
    case 0x06:
      return "compress";
    default:
      return "proc";
    }
}

static void
parse_processing_unit (ossusb_devc * devc, unsigned char *d, int l)
{
  usb_audio_unit_t *un;
  char *name;

  name = get_processor_type (d[4]);

  if ((un = setup_unit (devc, d[3], name, d, l, TY_PROC)) == NULL)
    return;
  un->subtyp = d[4];
  un->ctl_avail = 1;

  if (un->subtyp == 1)		// Upmix/downmix unit
    {
      un->channels = d[8];
      un->chmask = ossusb_get_int (&d[9], 2);
    }
}

static int
get_feature_mask (unsigned char *d, int channels)
{
  int i, n, mask = 0, v;
  n = d[5];

  for (i = 0; i <= channels; i++)
    {
      v = d[6 + i * n];
      if (n > 1)
	v |= d[6 + i * n + 1] << 8;

      mask |= v;
    }

  return mask;
}

#if 1
static void
mixer_dump (ossusb_devc * devc)
{
  int i, j, c;
  usb_audio_unit_t *un;

  for (i = 1; i < devc->nunits; i++)
    {
      un = &devc->units[i];

      if (un->typ == TY_FEAT)
	{
	  cmn_err (CE_CONT, "Unit %d\n", un->num);

	  for (j = 0; j < 8; j++)
	    {
	      for (c = 0; c < 7; c++)
		{
		  unsigned char buf[2];
		  int len;

		  buf[0] = buf[1] = 0;
		  len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
						 GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
						 ((j) << 8) | (c),	// value
						 (un->num << 8),	// index
						 buf,	// buffer
						 2,	// buflen
						 OSS_HZ * 2);

		  if (len < 0)
		    continue;
		  cmn_err (CE_CONT, "  Feature %d/%d, ch %d: ", un->num, j,
			   c);
		  cmn_err (CE_CONT, "%02x%02x ", buf[0], buf[1]);

		  if (len == 1)
		    cmn_err (CE_CONT, "(%d) ", (signed char) buf[0]);
		  else
		    cmn_err (CE_CONT, "(%d) ",
			     (signed short) (buf[0] | (buf[1] << 8)));

		  buf[0] = buf[1] = 0;
		  len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
						 GET_MIN, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
						 ((j) << 8) | (c),	// value
						 (un->num << 8),	// index
						 buf,	// buffer
						 2,	// buflen
						 OSS_HZ * 2);

		  if (len >= 0)
		    cmn_err (CE_CONT, "Min=%02x%02x ", buf[0], buf[1]);
		  if (len == 1)
		    cmn_err (CE_CONT, "(%d) ", (signed char) buf[0]);
		  else if (len == 2)
		    cmn_err (CE_CONT, "(%d) ",
			     (signed short) (buf[0] | (buf[1] << 8)));

		  buf[0] = buf[1] = 0;
		  len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
						 GET_MAX, USB_RECIP_INTERFACE | USB_TYPE_CLASS,	// rqtype
						 ((j) << 8) | (c),	// value
						 (un->num << 8),	// index
						 buf,	// buffer
						 2,	// buflen
						 OSS_HZ * 2);

		  if (len >= 0)
		    cmn_err (CE_CONT, "max=%02x%02x ", buf[0], buf[1]);
		  if (len == 1)
		    cmn_err (CE_CONT, "(%d) ", (signed char) buf[0]);
		  else if (len == 2)
		    cmn_err (CE_CONT, "(%d) ",
			     (signed short) (buf[0] | (buf[1] << 8)));

		  cmn_err (CE_CONT, "\n");
		}
	    }
	}
    }
}
#endif

/*ARGSUSED*/
static ossusb_devc *
ossusb_init_audioctl (ossusb_devc * devc, udi_usb_devc * usbdev, int inum,
		      int reinit)
{
  int desc_len;
  unsigned char *desc, *d;
  int i, l, n = 0, p, x, mixnum = -1;
  char *name;
  usb_audio_unit_t *un;

  /* nsettings = udi_usbdev_get_num_altsettings (usbdev); */
  devc->mixer_usbdev = usbdev;

  if (!init_mixer (devc))
    return NULL;

  desc = udi_usbdev_get_altsetting (usbdev, 0, &desc_len);

  if (desc == NULL)
    {
      cmn_err (CE_CONT, "Can't read interface descriptors\n");
      return NULL;
    }

  p = 0;
  while (p < desc_len)
    {
      d = desc + p;

      l = *d;
      if (usb_trace > 1)
	{
	  char str[256], *s = str;
	  sprintf (s, "Control desc(%d): ", p);
	  s += strlen (s);
	  for (i = 0; i < l; i++)
	    {
	      sprintf (s, "%02x ", d[i]);
	      s += strlen (s);
	    }
	  cmn_err (CE_CONT, "%s\n", str);
	}

#define CASE(x) case x:cmn_err(CE_CONT, #x "\n"); break;

      if (d[1] == CS_INTERFACE)
	switch (d[2])
	  {
	  case AC_HEADER:
	    if (usb_trace)
	      {
		cmn_err (CE_CONT, "	Audio control interface header\n");
		n = d[7];
		cmn_err (CE_CONT, "	%d related streaming interfaces: ",
			 n);
		for (i = 0; i < n; i++)
		  {
		    cmn_err (CE_CONT, "%d ", d[8 + i]);
		  }
		cmn_err (CE_CONT, "\n");
	      }
	    break;

	  case AC_INPUT_TERMINAL:
	    name =
	      get_terminal_id (ossusb_get_int (&d[4], 2), "in", TY_INPUT,
			       &mixnum);
	    if ((un = setup_unit (devc, d[3], name, d, l, TY_INPUT)) == NULL)
	      return NULL;
	    un->mixnum = mixnum;
	    un->channels = d[7];
	    un->chmask = ossusb_get_int (&d[8], 2);
	    break;

	  case AC_OUTPUT_TERMINAL:
	    name =
	      get_terminal_id (ossusb_get_int (&d[4], 2), "out", TY_OUTPUT,
			       &mixnum);
	    if ((un = setup_unit (devc, d[3], name, d, l, TY_OUTPUT)) == NULL)
	      return NULL;
	    un->mixnum = mixnum;
	    break;

	  case AC_MIXER_UNIT:
	    if ((un = setup_unit (devc, d[3], "mix", d, l, TY_MIXER)) == NULL)
	      return NULL;
	    {
	      // Check if there are any visible controls

	      int mask = 0, nn;

	      n = d[4];		// # of input pins
	      d += 5 + n;
	      nn = *d;		// # of channels
	      d += 4;		// Seek to bmControls

	      n = (n * nn + 7) / 8;

	      for (i = 0; i < n; i++)
		mask |= d[i];

	      un->ctl_avail = mask;
	    }
	    break;

	  case AC_SELECTOR_UNIT:
	    if ((un =
		 setup_unit (devc, d[3], "src", d, l, TY_SELECT)) == NULL)
	      return NULL;
	    un->ctl_avail = 1;
	    break;

	  case AC_FEATURE_UNIT:
	    if ((un = setup_unit (devc, d[3], "fea", d, l, TY_FEAT)) == NULL)
	      return NULL;
	    un->ctl_avail = get_feature_mask (d, 2);	/* For now */
	    break;

	  case AC_PROCESSING_UNIT:
	    parse_processing_unit (devc, d, l);
	    break;

	  case AC_EXTENSION_UNIT:
	    if ((un = setup_unit (devc, d[3], "ext", d, l, TY_EXT)) == NULL)
	      return NULL;
	    un->ctl_avail = 1;

	    break;

	  }

      p += l;
    }

// Make sure the unit names are unique */

  for (x = 1; x < devc->nunits; x++)
    {
      usb_audio_unit_t *un = &devc->units[x];
      int n = 0;

      if (un->typ != TY_SELECT)
	for (i = x + 1; i < devc->nunits; i++)
	  if (strcmp (devc->units[i].name, un->name) == 0)
	    n++;

      if (n > 0)
	{
	  char tmpname[16];
	  strcpy (tmpname, un->name);
	  n = 1;

	  for (i = x; i < devc->nunits; i++)
	    if (strcmp (devc->units[i].name, tmpname) == 0)
	      {
		if (n > 1)
		  sprintf (devc->units[i].name, "%s%d", tmpname, n);
		n++;
	      }
	}

// Make sure the mixer control numbers are unique too

      n = 0;
      if (un->mixnum != -1)
	for (i = x + 1; i < devc->nunits; i++)
	  if (devc->units[i].mixnum == un->mixnum)
	    n++;

      if (n > 0)
	for (i = x + 1; i < devc->nunits; i++)
	  if (devc->units[i].mixnum == un->mixnum)
	    {
	      usb_audio_unit_t *uu = &devc->units[i];

	      switch (uu->mixnum)
		{
		case SOUND_MIXER_PCM:
		  uu->mixnum = SOUND_MIXER_ALTPCM;
		  break;
		case SOUND_MIXER_LINE:
		  uu->mixnum = SOUND_MIXER_LINE1;
		  break;
		case SOUND_MIXER_LINE1:
		  uu->mixnum = SOUND_MIXER_LINE2;
		  break;
		case SOUND_MIXER_LINE2:
		  uu->mixnum = SOUND_MIXER_LINE3;
		  break;
		case SOUND_MIXER_DIGITAL1:
		  uu->mixnum = SOUND_MIXER_DIGITAL2;
		  break;
		case SOUND_MIXER_DIGITAL2:
		  uu->mixnum = SOUND_MIXER_DIGITAL3;
		  break;
		default:
		  uu->mixnum = -1;
		}
	    }

    }

// Handle output selector names


  for (x = 1; x < devc->nunits; x++)
    {
      usb_audio_unit_t *un = &devc->units[x];
      int n;

      if (un->typ != TY_OUTPUT)
	continue;

      n = un->desc[7];		// Source ID
      if (n < 0 || n >= devc->nunits)
	continue;

      if (devc->units[n].target == 0)
	devc->units[n].target = x;

      if (devc->units[n].typ == TY_SELECT && usb_mixerstyle == 0)
	sprintf (devc->units[n].name, "%s.src", un->name);
    }

// Find out the sources
  for (x = 1; x < devc->nunits; x++)
    {
      usb_audio_unit_t *un = &devc->units[x];

      d = un->desc;
      l = un->desclen;

      switch (un->typ)
	{
	case TY_INPUT:
	  break;

	case TY_OUTPUT:
	  un->source = d[7];
	  break;

	case TY_SELECT:
	  un->source = d[5];
	  n = d[4];
	  for (i = 0; i < n; i++)
	    if (d[i + 5] > 0 && d[i + 5] <= devc->nunits)
	      if (devc->units[d[i + 5]].target == 0)
		devc->units[d[i + 5]].target = x;
	  break;

	case TY_MIXER:
	  n = d[4];
	  un->control_count = n;
	  un->source = d[5];
	  for (i = 0; i < n; i++)
	    if (d[i + 5] > 0 && d[i + 5] <= devc->nunits)
	      if (devc->units[d[i + 5]].target == 0)
		devc->units[d[i + 5]].target = x;

	  d += n + 5;
	  un->channels = *d++;
	  un->chmask = ossusb_get_int (d, 2);
	  break;

	case TY_FEAT:
	  un->source = d[4];
	  if (d[4] > 0 && d[4] <= devc->nunits)
	    if (devc->units[d[4]].target == 0)
	      devc->units[d[4]].target = x;
	  break;

	  //case TY_EXT: 
	case TY_PROC:
	  n = d[6];
	  un->source = d[7];
	  for (i = 0; i < n; i++)
	    if (d[i + 7] > 0 && d[i + 7] <= devc->nunits)
	      if (devc->units[d[i + 7]].target == 0)
		devc->units[d[i + 7]].target = x;
	  break;
	}
    }

// Trace the channel config

  for (x = 1; x < devc->nunits; x++)
    {
      usb_audio_unit_t *un = &devc->units[x], *uu;
      int ref;

      if (un->typ == TY_INPUT || un->typ == TY_MIXER)
	continue;

      if (un->typ == TY_PROC && un->subtyp == 1)	// Upmix/downmix unit
	continue;

      ref = follow_source_links (devc, un);
      uu = &devc->units[ref];

      un->channels = uu->channels;
      un->chmask = uu->chmask;
    }

// Handle feature channels
  for (x = 1; x < devc->nunits; x++)
    {
      usb_audio_unit_t *un = &devc->units[x];

      if (un->num == 0 || un->typ != TY_FEAT)
	continue;

      d = un->desc;
      l = un->desclen;

      un->ctl_avail = get_feature_mask (d, un->channels);
    }

// Final checks
  for (x = 1; x < devc->nunits; x++)
    {
      usb_audio_unit_t *un = &devc->units[x];
      int j;

      if (un->num == 0)
	{
	  //cmn_err(CE_CONT, "Skipped undefined control %d\n", x);
	  continue;
	}
      d = un->desc;
      l = un->desclen;

      switch (un->typ)
	{
	case TY_SELECT:
	  n = d[4];
	  un->control_count = n;
	  d += 5;
	  break;

	case TY_MIXER:
	  n = d[4];
	  un->control_count = n;
	  d += 5;
	  break;

	case TY_FEAT:
	  n = d[5];
	  d += 6;
	  for (i = 0; i < n * 8; i++)
	    name = check_feature (un, d, i);
	  for (j = 1; j <= un->channels; j++)
	    {
	      for (i = 0; i < n * 8; i++)
		name = check_feature (un, d + j * n, i);
	    }
	  break;

	}
    }

#if 0
// Debugging
  if (usb_trace)
    for (x = 1; x < devc->nunits; x++)
      {
	usb_audio_unit_t *un = &devc->units[x];
	int j;
	int ref = 0;

	if (un->num == 0)
	  {
	    //cmn_err(CE_CONT, "Skipped undefined control %d\n", x);
	    continue;
	  }
	d = un->desc;
	l = un->desclen;
	// ossusb_dump_desc (d, l);
	cmn_err (CE_CONT, "%2d: %s  ", un->num, un->name);
	if (un->mixnum != -1)
	  cmn_err (CE_CONT, "mix=%d ", un->mixnum);
	if (un->source)
	  cmn_err (CE_CONT, "source=%d ", un->source);
	if (un->target)
	  cmn_err (CE_CONT, "target=%d ", un->target);
	cmn_err (CE_CONT, "ch=%d/%x ", un->channels, un->chmask);

	switch (un->typ)
	  {
	  case TY_INPUT:
	    cmn_err (CE_CONT, "Input terminal type: %04x ",
		     ossusb_get_int (&d[4], 2));
	    cmn_err (CE_CONT, "Associated output 0x%02x ", d[6]);
	    cmn_err (CE_CONT, "#chn %d ", d[7]);
	    cmn_err (CE_CONT, "chconf %04x ", ossusb_get_int (&d[8], 2));
	    if (d[10])
	      cmn_err (CE_CONT, "chname# %d ", d[10]);
	    if (d[11])
	      cmn_err (CE_CONT, "terminalname# %d (%s) ", d[11],
		       udi_usbdev_get_string (usbdev, d[11]));
	    break;

	  case TY_OUTPUT:
	    cmn_err (CE_CONT, "Output terminal type: %04x ",
		     ossusb_get_int (&d[4], 2));
	    cmn_err (CE_CONT, "Associated input 0x%02x ", d[6]);
	    cmn_err (CE_CONT, "sourceid %d ", d[7]);
	    if (d[8])
	      cmn_err (CE_CONT, "terminalname# %d ", d[8]);
	    break;

	  case TY_SELECT:
	    n = d[4];
	    d += 5;
	    cmn_err (CE_CONT, "%d input pins (", n);
	    for (i = 0; i < n; i++)
	      {
		ref = follow_source_links (devc, &devc->units[*d]);
		cmn_err (CE_CONT, "%d/%s ", *d, devc->units[ref].name);
		d++;
	      }
	    cmn_err (CE_CONT, ") ");
	    break;

	  case TY_MIXER:
	    n = d[4];
	    d += 5;
	    cmn_err (CE_CONT, "%d inputs (", n);
	    for (i = 0; i < n; i++)
	      {
		ref = follow_source_links (devc, &devc->units[*d]);
		cmn_err (CE_CONT, "%d/%s ", *d, devc->units[ref].name);
		d++;
	      }
	    cmn_err (CE_CONT, ") ");
	    break;

	  case TY_FEAT:
	    //ossusb_dump_desc(d, l);
	    cmn_err (CE_CONT, "Source %d:%s ", d[4], devc->units[d[4]].name);
	    n = d[5];
	    d += 6;
	    cmn_err (CE_CONT, "main (", n);
	    for (i = 0; i < n * 8; i++)
	      if ((name = check_feature (un, d, i)) != NULL)
		cmn_err (CE_CONT, "%s ", name);
	    cmn_err (CE_CONT, ") ");
	    for (j = 1; j <= un->channels; j++)
	      {
		cmn_err (CE_CONT, "ch %d (", j);
		for (i = 0; i < n * 8; i++)
		  if ((name = check_feature (un, d + j * n, i)) != NULL)
		    cmn_err (CE_CONT, "%s ", name);
		cmn_err (CE_CONT, ") ");
	      }
	    break;

	  case TY_PROC:
	    cmn_err (CE_CONT, "subtype %x Sources/%d) (", un->subtyp, n);
	    n = d[6];
	    d += 7;
	    cmn_err (CE_CONT, "%d ", *d);
	    d++;
	    cmn_err (CE_CONT, ") ");
	    break;

	  case TY_EXT:
	    cmn_err (CE_CONT, "Extension unit %02x%02x ", d[5], d[4]);
	    break;

	  }

	cmn_err (CE_CONT, "\n");
      }
#endif

  if (usb_mixerstyle == 0)
    setup_legacy_mixer (devc);
  touch_mixer (devc->mixer_dev);

  if (usb_trace)
    mixer_dump (devc);

  return devc;
}

static ossusb_devc *
find_devc (char *devpath, int vendor, int product)
{
  int i;

  for (i = 0; i < ndevs; i++)
    {
      if (devc_list[i]->vendor == vendor && devc_list[i]->product == product)
	if (strcmp (devc_list[i]->devpath, devpath) == 0)
	  {
	    UDB (cmn_err (CE_CONT, "Another instance of '%s'\n", devpath));
	    return devc_list[i];
	  }
    }

  return NULL;
}

static void
ossusb_device_disconnect (void *d)
{
  ossusb_devc *devc = d;
  int i;

  if (devc == NULL)
    {
      cmn_err (CE_WARN, "oss_usb_device_disconnect: devc==NULL\n");
      return;
    }

  if (devc->unload_func)
    {
      devc->unload_func (devc);
      return;
    }

  devc->disabled = 1;

  for (i = 0; i < devc->num_audio_engines; i++)
    {
      int dev;

      dev = devc->portc[i].audio_dev;

      if (dev < 0 || dev >= num_audio_engines)
	continue;

      audio_engines[dev]->enabled = 0;

      if (devc->mixer_dev >= 0)
	mixer_devs[devc->mixer_dev]->enabled = 0;
    }

}

static void *
ossusb_device_attach (udi_usb_devc * usbdev, oss_device_t * osdev)
{
  ossusb_devc *devc;
  char *devpath;
  int inum;
  int old = 1;
  int i;
  int class, subclass;
  int vendor, product, version;

  devpath = udi_usbdev_get_devpath (usbdev);
  inum = udi_usbdev_get_inum (usbdev);
  class = udi_usbdev_get_class (usbdev);
  subclass = udi_usbdev_get_subclass (usbdev);
  vendor = udi_usbdev_get_vendor (usbdev);
  product = udi_usbdev_get_product (usbdev);
  version = udi_usbdev_get_usb_version (usbdev);

  if ((devc = find_devc (devpath, vendor, product)) == NULL)
    {
      old = 0;

      if (ndevs >= MAX_DEVC)
	{
	  cmn_err (CE_CONT, "Too many USB audio devices\n");
	  return NULL;
	}

      if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL)
	{
	  cmn_err (CE_CONT, "Out of memory\n");
	  return NULL;
	}
      memset (devc, 0, sizeof (*devc));
      devc->mixer_dev = -1;

      devc->osdev = osdev;
      osdev->devc = devc;

      MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV);

      devc->vendor = vendor;
      devc->product = product;
      devc->usb_version = version;
      devc->dev_name = udi_usbdev_get_name (usbdev);

      strcpy (devc->devpath, udi_usbdev_get_devpath (usbdev));

      devc_list[ndevs++] = devc;
    }
  else
    {
      devc->osdev = osdev;
      osdev->devc = devc;
    }

  if (devc->dev_name == NULL)
    devc->dev_name = "Generic USB device";
  oss_register_device (osdev, devc->dev_name);

  devc->disabled = 0;

  if (old)
    {
      /* Check if this interface number is already seen */
      old = 0;
      for (i = 0; !old && i < devc->num_interfaces; i++)
	if (devc->inum[i] == inum)
	  old = 1;
    }

  if (!old)
    {
      if (devc->num_interfaces >= MAX_IFACE)
	{
	  cmn_err (CE_CONT, "The device has too many interfaces\n");
	  return NULL;
	}

      devc->usbdev[devc->num_interfaces] = usbdev;
      devc->last_usbdev = usbdev;
      devc->inum[devc->num_interfaces] = inum;
      devc->num_interfaces++;
    }
  else
    {
      devc->last_usbdev = usbdev;
    }

  switch (class)
    {
    case USBCLASS_AUDIO:
      switch (subclass)
	{
	case 1:		/* Audiocontrol subclass */
	  devc->main_osdev = osdev;
	  if (!usb_quiet)
	    cmn_err (CE_CONT, "%s audioctl device %s/%d - %s\n",
		     old ? "Reinsert of an" : "New",
		     devc->devpath, inum, devc->dev_name);
	  return ossusb_init_audioctl (devc, usbdev, inum, old);
	  break;

	case 2:		/* Audio streaming subclass */
	  if (!usb_quiet)
	    cmn_err (CE_CONT, "%s audio streaming device %s/%d - %s\n",
		     old ? "Reinsert of an" : "New",
		     devc->devpath, inum, devc->dev_name);
	  devc->osdev->first_mixer = devc->main_osdev->first_mixer;
	  return ossusb_init_audiostream (devc, usbdev, inum, old);
	  break;

	case 3:		/* MIDI streaming subclass */
	  return NULL;
	  break;

	default:
	  cmn_err (CE_CONT,
		   "Unknown USB audio device subclass %x:%d, device=%s\n",
		   class, subclass, devc->dev_name);
	  return NULL;
	}
      break;

#if 0
    case USBCLASS_HID:
      cmn_err (CE_CONT, "HID interface class %x:%d, device=%s\n",
	       class, subclass, devc->dev_name);
      return NULL;
      break;
#endif

    default:
      cmn_err (CE_CONT, "Unknown USB device class %x:%d, device=%s\n",
	       class, subclass, devc->dev_name);
    }

  return devc;
}

static udi_usb_driver ossusb_driver = {
  ossusb_device_attach,
  ossusb_device_disconnect
};

int
oss_usb_attach (oss_device_t * osdev)
{
  if (usb_trace < 0)
    {
      udi_usb_trace = 0;
      usb_trace = 0;
      usb_quiet = 1;
    }
  else if (usb_trace > 0)
    udi_usb_trace = usb_trace;

  return udi_attach_usbdriver (osdev, known_devices, &ossusb_driver);
}

int
oss_usb_detach (oss_device_t * osdev)
{
  if (oss_disable_device (osdev) < 0)
    return 0;

  udi_unload_usbdriver (osdev);
  oss_unregister_device (osdev);

  return 1;
}
