/*
 * Purpose: USB audio streaming interface support
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

/*
 * NOTE! This is not the development version of this file. Please download the
 * sources of the latest Open Sound System version from 
 * http://developer.opensound.com/sources if you plan to make any changes
 * to the software.
 */

#include "oss_config.h"
#include "ossusb.h"

#define SAMPLING_FREQ_CONTROL      0x01
#define PITCH_CONTROL              0x02

#define FORMAT_II_UNDEFINED		0x1000
#define FORMAT_II_MPEG			0x1001
#define FORMAT_II_AC3			0x1002

#define TMPBUF_SIZE	4096

#if 0
static int
read_control_value (ossusb_devc * devc, int endpoint, int ctl, int l)
{
  unsigned char buf[4];
  int len, i, v;

  memset (buf, 0, sizeof (buf));

  len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0,	// endpoint
				 GET_CUR, USB_RECIP_ENDPOINT | USB_TYPE_CLASS,	// rqtype
				 ctl << 8,	// value
				 endpoint,	// index
				 buf,	// buffer
				 l,	// buflen
				 OSS_HZ);
  if (len < 0)
    {
      cmn_err (CE_CONT, "ossusb: Endpoint read error %d\n", len);
      return 0;
    }

  cmn_err (CE_CONT, "Read len %d (%d): ", len, l);
  for (i = 0; i < len; i++)
    cmn_err (CE_CONT, "%02x ", buf[i]);

  switch (len)
    {
    case 3:
      v = buf[0] | (buf[1] << 8) | (buf[2] << 16);
      break;

    default:
      cmn_err (CE_CONT, "oss usbaudio: Bad control read (%d)\n", l);
    }

  cmn_err (CE_CONT, "= %d\n", v);

  return v;
}
#endif

static int
write_control_value (ossusb_devc * devc, udi_endpoint_handle_t * endpoint,
		     int ctl, int l, unsigned int v)
{
  unsigned char buf[4];
  int len;

  memset (buf, 0, sizeof (buf));

  switch (l)
    {
    case 3:
      buf[0] = (v) & 0xff;
      buf[1] = (v >> 8) & 0xff;
      buf[2] = (v >> 16) & 0xff;
      break;

    default:
      cmn_err (CE_CONT, "oss usbaudio: Bad control size %d\n", l);
      return -EIO;
    }

  len = udi_usb_snd_control_msg (devc->mixer_usbdev, 0,	// endpoint
				 SET_CUR, USB_RECIP_ENDPOINT | USB_TYPE_CLASS,	// rqtype
				 ctl << 8,	// value
				 udi_endpoint_get_num (endpoint),	// index
				 buf,	// buffer
				 l,	// buflen
				 OSS_HZ);
  if (len < 0)
    {
      cmn_err (CE_CONT, "ossusb: Endpoint control write error %d\n", len);
      return -EIO;
    }

  return len;
}

static void
set_fraglimits (adev_t * adev, ossusb_portc * portc)
{
  int l, m;

  if (portc->bytes_per_sample < 1)
    portc->bytes_per_sample = 1;

  l = portc->bytes_per_sample * adev->min_channels * portc->speed / 1000;
  l = (l / portc->bytes_per_sample) * portc->bytes_per_sample;

  m = 2;

  while (m < l)
    m *= 2;

  adev->min_block = m;
  adev->max_block = TMPBUF_SIZE / 2;
  portc->fragment_size = l;
}

static int
usbaudio_set_rate (int dev, int arg)
{
  adev_p adev = audio_engines[dev];
  ossusb_portc *portc = adev->portc;
  ossusb_devc *devc = adev->devc;

  int i, x, diff, bestdiff, l;

  if (devc->disabled)
    return -EPIPE;

  if (arg == 0)
    return portc->speed;

  if (arg < adev->min_rate)
    arg = adev->min_rate;
  if (arg > adev->max_rate)
    arg = adev->max_rate;

  if (adev->caps = PCM_CAP_FREERATE)
    {
      //arg=48000;
    }
  else
    {
      /* Search for the nearest supported rate */
      bestdiff = 0x7fffffff;
      x = -1;

      for (i = 0; i < adev->nrates; i++)
	{
	  diff = arg - adev->rates[i];

	  if (diff < 0)
	    diff = -diff;	/* ABS */
	  if (diff < bestdiff)
	    {
	      x = i;
	      bestdiff = diff;
	    }
	}

      if (x > -1)
	arg = adev->rates[x];
    }

  portc->speed = arg;
  set_fraglimits (adev, portc);

  return portc->speed;
}

static short
usbaudio_set_channels (int dev, short arg)
{
  adev_p adev = audio_engines[dev];
  ossusb_devc *devc = adev->devc;

  if (devc->disabled)
    return -EPIPE;

  return adev->min_channels;	/* max_channels should be the same too */
}

static unsigned int
usbaudio_set_format (int dev, unsigned int arg)
{
  adev_p adev = audio_engines[dev];
  ossusb_devc *devc = adev->devc;

  if (devc->disabled)
    return -EPIPE;

  return adev->oformat_mask;	/* iformat_mask should be the same  too */
}

static int
usbaudio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  if (devc->disabled)
    return -EPIPE;

  return -EINVAL;
}

static int
setup_format_I (ossusb_devc * devc, ossusb_portc * portc, adev_p adev,
		unsigned char *d, int l)
{
  int min_rate = 0, max_rate = 0;
  int i, n;
  int frame_size, bits, channels;
  unsigned int fmt = 0;

  if (usb_trace > 1)
    {
      cmn_err (CE_CONT,
	       "AS_FORMAT_TYPE: FORMAT_TYPE_%d, #ch %d, framesize %d, bits %d, freq_type %02x, freq=",
	       d[4], d[5], d[6], d[7]);
      if (d[7] == 0)
	cmn_err (CE_CONT, "%d-%d ", ossusb_get_int (&d[8], 3),
		 ossusb_get_int (&d[11], 3));
      {
	for (i = 0; i < d[7]; i++)
	  cmn_err (CE_CONT, "%d ", ossusb_get_int (&d[8 + i * 3], 3));
	cmn_err (CE_CONT, " ");
      }

      switch (d[3])
	{
	case 0x00:
	  cmn_err (CE_CONT, "Undefined ");
	  break;
	case 0x01:
	  cmn_err (CE_CONT, "PCM ");
	  break;
	case 0x02:
	  cmn_err (CE_CONT, "PCM8 ");
	  break;
	case 0x03:
	  cmn_err (CE_CONT, "float ");
	  break;
	case 0x04:
	  cmn_err (CE_CONT, "A-Law ");
	  break;
	case 0x05:
	  cmn_err (CE_CONT, "u-Law ");
	  break;
	}
      cmn_err (CE_CONT, "\n");
    }

  channels = d[4];
  frame_size = d[5];
  bits = d[6];
  UDB (cmn_err (CE_CONT, "Channels %d, bits %d (%d bytes)\n", channels, bits,
		frame_size));

  adev->min_channels = adev->max_channels = channels;
  portc->convert_3byte = 0;

  switch (d[3])
    {
    case 0x01:
      fmt = AFMT_S16_LE;
      break;
    case 0x02:
      fmt = AFMT_U8;
      break;
    case 0x03:
      fmt = AFMT_FLOAT;
      break;
    case 0x04:
      fmt = AFMT_A_LAW;
      break;
    case 0x05:
      fmt = AFMT_MU_LAW;
      break;
    }

  if (fmt == AFMT_S16_LE)	/* Have to check the frame size too */
    {
      switch (frame_size)
	{
	case 1:
	  fmt = AFMT_S8;
	  break;
	case 2:
	  fmt = AFMT_S16_LE;
	  break;
	case 3:
	  fmt = AFMT_S32_LE;
	  frame_size = 4;
	  portc->convert_3byte = 1;
	  break;
	case 4:
	  fmt = AFMT_S32_LE;
	  break;
	}
    }

  portc->bytes_per_sample = frame_size;
  adev->oformat_mask = adev->iformat_mask = fmt;
  UDB (cmn_err (CE_CONT, "Format mask %08x\n", fmt));

  adev->caps &= ~PCM_CAP_FREERATE;
  n = d[7];
  if (n < 1)			/* Free rate selection between min (0) and max (1) */
    {
      n = 2;
      adev->caps |= PCM_CAP_FREERATE;
    }

  min_rate = 0x7fffffff;
  max_rate = 0;

  if (n > 20)
    {
      cmn_err (CE_CONT,
	       "ossusb: The device supports too many sample rates\n");
      n = 20;
    }

  adev->nrates = 0;

  for (i = 0; i < n; i++)
    {
      int rate = ossusb_get_int (&d[8 + i * 3], 3);

#if 0
      /* Skip rates that are not multiples of 1000 Hz */
      if (rate % 1000)
	continue;
#endif

      if (rate < min_rate)
	min_rate = rate;
      if (rate > max_rate)
	max_rate = rate;
      adev->rates[adev->nrates++] = rate;
    }

  adev->min_rate = min_rate;
  adev->max_rate = max_rate;
  UDB (cmn_err (CE_CONT, "Min rate %d, max rate %d\n", min_rate, max_rate));

  adev->caps &= ~DSP_CH_MASK;

  switch (channels)
    {
    case 1:
      adev->caps |= DSP_CH_MONO;
      break;
    case 2:
      adev->caps |= DSP_CH_STEREO;
      break;
    default:
      adev->caps |= DSP_CH_MULTI;
      break;
    }
}

static int
setup_format_II (ossusb_devc * devc, ossusb_portc * portc, adev_p adev,
		 unsigned char *d, int l)
{
  int min_rate = 0, max_rate = 0;
  int i;

  if (usb_trace > 1)
    {
      cmn_err (CE_CONT, "MaxBitRate %d ", d[4]);
      cmn_err (CE_CONT, "SamplesPerFrame %d ", d[5]);

      if (d[8] == 0)
	cmn_err (CE_CONT, "Sample Rates %d-%d ", ossusb_get_int (&d[9], 3),
		 ossusb_get_int (&d[12], 3));
      else
	{
	  int n;
	  int min_rate = 0x7fffffff;
	  int max_rate = 0;

	  n = d[8];

	  if (n > 20)
	    {
	      cmn_err (CE_CONT, "oss usbaudio: Too many sample rates (%d)\n",
		       n);
	      n = 20;
	    }

	  adev->nrates = 0;
	  cmn_err (CE_CONT, "Possible sample rates: ");
	  for (i = 0; i < d[8]; i++)
	    {
	      int rate = ossusb_get_int (&d[9 + i * 3], 3);

#if 0
	      /* Skip rates that are not multiples of 1000 Hz */
	      if (rate % 1000)
		continue;
#endif

	      if (rate < min_rate)
		min_rate = rate;
	      if (rate > max_rate)
		max_rate = rate;
	      cmn_err (CE_CONT, "%d ", rate);
	      adev->rates[adev->nrates++] = rate;
	    }
	  adev->min_rate = min_rate;
	  adev->max_rate = max_rate;
	}

      cmn_err (CE_CONT, "\n");
    }

  adev->caps &= ~PCM_CAP_FREERATE;
  if (d[8] == 0)
    {
      min_rate = ossusb_get_int (&d[9], 3);
      max_rate = ossusb_get_int (&d[12], 3);
      adev->caps |= PCM_CAP_FREERATE;
    }
  else
    {
      min_rate = 1 << 30;
      max_rate = 0;

      for (i = 0; i < d[8]; i++)
	{
	  int r = ossusb_get_int (&d[9 + i * 3], 3);

	  if (r < min_rate)
	    min_rate = r;
	  if (r > max_rate)
	    max_rate = r;
	}
    }

  adev->min_channels = adev->max_channels = 2;
  adev->oformat_mask = adev->iformat_mask = AFMT_AC3;
  adev->min_rate = min_rate;
  adev->max_rate = max_rate;

  return 0;
}

static int
setup_format_specific (ossusb_devc * devc, ossusb_portc * portc, adev_p adev,
		       unsigned char *d, int l)
{
  int fmt;

  fmt = ossusb_get_int (&d[3], 2);

  if (usb_trace > 1)
    cmn_err (CE_CONT, "Format specific: fmt=%04x\n", fmt);

  switch (fmt)
    {
    case FORMAT_II_MPEG:
      if (usb_trace > 1)
	cmn_err (CE_CONT, "MPEG format\n");
      adev->oformat_mask = adev->iformat_mask = AFMT_MPEG;
      break;

    case FORMAT_II_AC3:
      if (usb_trace > 1)
	cmn_err (CE_CONT, "AC3 format\n");
      adev->oformat_mask = adev->iformat_mask = AFMT_AC3;
#if 0
      cmn_err (CE_CONT, "BSID=%08x\n", ossusb_get_int (&d[5], 4));
      cmn_err (CE_CONT, "AC3Features %02x\n", d[9]);
#endif
      break;

    default:
      cmn_err (CE_CONT, "oss usbaudio: Unsupported FORMAT II tag %04x\n",
	       fmt);
      adev->enabled = 0;
      return -ENXIO;
    }
  return 0;
}

static int
prepare_altsetting (ossusb_devc * devc, ossusb_portc * portc)
{
  int nsettings, desc_len;
  unsigned char *desc, *d;
  int l, p;
  int err;
  adev_p adev;

  adev = audio_engines[portc->audio_dev];
  adev->enabled = 1;
  portc->disabled = 0;

  if (portc->act_setting == devc->act_setting)	/* No need to change */
    return 0;

  nsettings = udi_usbdev_get_num_altsettings (portc->usbdev);

  if (devc->act_setting < portc->num_settings)
    desc =
      udi_usbdev_get_altsetting (portc->usbdev, devc->act_setting, &desc_len);
  else
    desc = NULL;

  if (desc == NULL || desc_len < 3)
    {
      cmn_err (CE_CONT,
	       "ossusb: Audio device %d not available when altsetting=%d\n",
	       portc->audio_dev, devc->act_setting);
      portc->disabled = 1;
      adev->enabled = 0;
      portc->act_setting = devc->act_setting;
      return -ENXIO;
    }

  UDB (cmn_err
       (CE_CONT, "Select active settiong %d on interface %d (dsp%d)\n",
	devc->act_setting, portc->if_number, adev->engine_num));
  portc->act_setting = devc->act_setting;

  p = 0;
  while (p < desc_len)
    {
      int i;

      d = desc + p;
      l = *d;

      if (usb_trace > 1)
	{
	  cmn_err (CE_CONT, "Streaming desc: ");
	  for (i = 0; i < l; i++)
	    cmn_err (CE_CONT, "%02x ", d[i]);
	  cmn_err (CE_CONT, "\n");

	}

      if (d[1] != CS_INTERFACE)
	{
	UDB (cmn_err (CE_CONT, "Unknown descriptor type %02x\n", d[1]))}
      else
	switch (d[2])
	  {
	  case AS_GENERAL:
	    portc->terminal_link = d[3];
	    portc->pipeline_delay = d[4];

	    if (usb_trace > 1)
	      {
		cmn_err (CE_CONT, "AS_GENERAL ");
		cmn_err (CE_CONT, "Terminal link %d/%s ", d[3],
			 devc->units[d[3]].name);
		cmn_err (CE_CONT, "Delay %d ", d[4]);
		cmn_err (CE_CONT, "Format tag %02x%02x ", d[5], d[6]);
		cmn_err (CE_CONT, "\n");
	      }
	    break;

	  case AS_FORMAT_TYPE:
	    if (usb_trace > 1)
	      {
		cmn_err (CE_CONT, "AS_FORMAT_TYPE: FORMAT_TYPE_%d: ", d[3]);
	      }

	    switch (d[3])
	      {
	      case 1:		/* FORMAT_TYPE_I */
		if ((err = setup_format_I (devc, portc, adev, d, l)) < 0)
		  return err;
		break;

	      case 2:		/* FORMAT_TYPE_II */
		if ((err = setup_format_II (devc, portc, adev, d, l)) < 0)
		  return err;
		break;

	      default:
		cmn_err (CE_CONT,
			 "\noss usbaudio: Unsupported format type %d\n",
			 d[3]);
		adev->enabled = 0;
		return -ENXIO;
	      }
	    break;

	  case AS_FORMAT_SPECIFIC:
	    if ((err = setup_format_specific (devc, portc, adev, d, l)) < 0)
	      return err;
	    break;

	  default:
	    UDB (cmn_err
		 (CE_CONT, "Unknown descriptor subtype %02x\n", d[2]));
	  }

      p += l;
    }

  desc = udi_usbdev_get_endpoint (portc->usbdev, portc->act_setting, 0, &l);
  if (desc == NULL)
    {
      cmn_err (CE_CONT, "oss usbaudio: Bad endpoint\n");
      return -EIO;
    }

  portc->endpoint_desc = desc;

  desc = udi_usbdev_get_endpoint (portc->usbdev, portc->act_setting, 1, &l);
  if (desc != NULL)
    {
#if 0
      /* TODO: Handle sync endpoints */
      cmn_err (CE_CONT, "Sync Endpoint: ");
      ossusb_dump_desc (desc, l);
#endif
    }

  return 0;
}

static void usbaudio_close (int dev, int mode);

static int
usbaudio_open (int dev, int mode, int open_flags)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  ossusb_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags, phaddr;
  int err;
  int i;

  if (devc->disabled)
    return -EPIPE;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->open_mode != 0)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return -EBUSY;
    }
  portc->open_mode = mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  if ((err = prepare_altsetting (devc, portc)) < 0)
    {
      portc->open_mode = 0;
      return err;
    }

  {
    int i;

    for (i = 0; i < 2; i++)
      portc->tmp_buf[i] =
	CONTIG_MALLOC (devc->osdev, TMPBUF_SIZE, MEMLIMIT_32BITS, &phaddr);
  }

  if ((err =
       udi_usbdev_set_interface (portc->usbdev, portc->if_number,
				 devc->act_setting)) < 0)
    {
      cmn_err (CE_CONT,
	       "oss usbaudio: Failed to set interface mode, error %d\n", err);
      portc->open_mode = 0;
      return err;
    }

  portc->curr_datapipe = 0;

  if ((portc->endpoint_handle =
       udi_open_endpoint (portc->usbdev, portc->endpoint_desc)) == NULL)
    {
      usbaudio_close (dev, mode);
      cmn_err (CE_WARN, "Cannot open audio pipe\n");
      return -ENOMEM;
    }

  for (i = 0; i < 2; i++)
    {
      if ((portc->datapipe[i] =
	   udi_usb_alloc_request (portc->usbdev, portc->endpoint_handle, 1,
				  UDI_USBXFER_ISO_WRITE)) == NULL)
	{
	  usbaudio_close (dev, mode);
	  cmn_err (CE_WARN, "Cannot alloc isoc request\n");
	  return -ENOMEM;
	}
    }

  set_fraglimits (audio_engines[dev], portc);
  return 0;
}

static void usbaudio_reset (int dev);

static void
usbaudio_close (int dev, int mode)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  ossusb_portc *portc = audio_engines[dev]->portc;
  int i;

  usbaudio_reset (dev);

  for (i = 0; i < 2; i++)
    {
      if (portc->datapipe[i] != NULL)
	{
	  udi_usb_free_request (portc->datapipe[i]);
	  portc->datapipe[i] = NULL;
	}

      if (portc->tmp_buf[i] != NULL)
	{
	  CONTIG_FREE (devc->osdev, portc->tmp_buf[i], TMPBUF_SIZE);
	  portc->tmp_buf[i] = NULL;
	}
    }

  if (portc->endpoint_handle != NULL)
    udi_close_endpoint (portc->endpoint_handle);

  udi_usbdev_set_interface (portc->usbdev, portc->if_number, 0);
  portc->open_mode = 0;
}

static void
usbaudio_output_block (int dev, oss_native_word buf, int count,
		       int fragsize, int intrflag)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  if (devc->disabled)
    return;
}

static void
usbaudio_start_input (int dev, oss_native_word buf, int count, int fragsize,
		      int intrflag)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  if (devc->disabled)
    return;
}

static int feed_output (int dev, ossusb_devc * devc, ossusb_portc * portc);
static void start_input (int dev, ossusb_devc * devc, ossusb_portc * portc);

static int
copy_input (ossusb_portc * portc, dmap_t * dmap, unsigned char *buf, int len)
{
  int outlen = 0;
  int offs;

  offs = (int) (dmap->byte_counter % dmap->bytes_in_use);

  while (len > 0)
    {
      int l;
      l = len;

      if (portc->convert_3byte)
	{
	  int i, l, n;
	  int *dmabuf;

	  l = (l * 4) / 3;
	  /* Check for buffer wraparound */
	  if (offs + l > dmap->bytes_in_use)
	    l = dmap->bytes_in_use - offs;

	  n = (l / 4);

	  dmabuf = (int *) (dmap->dmabuf + offs);

	  for (i = 0; i < n; i++)
	    {
	      int v;

	      v = buf[2] | (buf[1] << 8) | (buf[0] << 16);
	      *dmabuf++ = v;
	      buf += 3;
	    }

	  outlen += l;
	}
      else
	{
	  unsigned char *dmabuf;
	  dmabuf = dmap->dmabuf;

	  /* Check for buffer wraparound */
	  if (offs + l > dmap->bytes_in_use)
	    l = dmap->bytes_in_use - offs;

	  memcpy (dmabuf + offs, buf, l);
	  outlen += l;
	  buf += l;
	}

      len -= l;
      offs = 0;
    }

  return outlen;
}

static void
play_callback (udi_usb_request_t * request, void *arg)
{
  ossusb_portc *portc = arg;

  feed_output (portc->audio_dev, portc->devc, portc);
  oss_audio_outputintr (portc->audio_dev,
			AINTR_NO_POINTER_UPDATES | AINTR_LOCALQUEUE);
}

static void
rec_callback (udi_usb_request_t * request, void *arg)
{
  ossusb_portc *portc = arg;
  dmap_t *dmap;
  int len;

  if (portc == NULL || (unsigned long) arg < 4096)
    {
      cmn_err (CE_WARN, "Bad portc %x\n", arg);
      return;
    }

  dmap = audio_engines[portc->audio_dev]->dmap_in;
  len = udi_usb_request_actlen (request);

  if (len == 0)
    return;			/* No data so it looks like we are closing down */

  if ((len =
       copy_input (portc, dmap, udi_usb_request_actdata (request), len)) < 1)
    {
      cmn_err (CE_WARN, "Saving recorded data failed (%d)\n", len);
      return;
    }

  oss_audio_inc_byte_counter (dmap, len);
  oss_audio_inputintr (portc->audio_dev, AINTR_NO_POINTER_UPDATES);
#ifdef linux
  start_input (portc->audio_dev, portc->devc, portc);
#endif
}

#if 0
/*
 * Testing stuff only
 */
static int
sin_gen (void)
{

  static int phase = 0, v;

  static short sinebuf[48] = {
    0, 4276, 8480, 12539, 16383, 19947, 23169, 25995,
    28377, 30272, 31650, 32486, 32767, 32486, 31650, 30272,
    28377, 25995, 23169, 19947, 16383, 12539, 8480, 4276,
    0, -4276, -8480, -12539, -16383, -19947, -23169, -25995,
    -28377, -30272, -31650, -32486, -32767, -32486, -31650, -30272,
    -28377, -25995, -23169, -19947, -16383, -12539, -8480, -4276
  };
  v = sinebuf[phase] * 256;
  phase = (phase + 1) % 48;

  return v;
}
#endif

static int
output_convert (ossusb_devc * devc, ossusb_portc * portc, dmap_p dmap,
		unsigned char *dmabuf, int pos, int pn, int total_len)
{
  unsigned char *tmpbuf = portc->tmp_buf[pn], *b;
  int i, n, len, loop, out_size = 0;
  int err;

  while (total_len > 0)
    {
      int l = total_len;

      /* Check for buffer wraparound */
      if (pos + l > dmap->bytes_in_use)
	l = dmap->bytes_in_use - pos;

      total_len -= l;

      b = dmabuf + pos;

      if (portc->convert_3byte)
	{
	  int *buf;

	  n = l / sizeof (*buf);
	  buf = (int *) b;

	  len = n * 3;

	  for (i = 0; i < n; i++)
	    {
	      int val = (*buf++);

	      val /= 256;
	      // val=sin_gen();
	      *tmpbuf++ = (val) & 0xff;
	      *tmpbuf++ = (val >> 8) & 0xff;
	      *tmpbuf++ = (val >> 16) & 0xff;
	    }
	}
      else
	{
	  len = l;
	  memcpy (tmpbuf, b, l);
	  tmpbuf += l;
	}

      pos = 0;
      out_size += len;
    }

  if ((err =
       udi_usb_submit_request (portc->datapipe[pn], play_callback, portc,
			       portc->endpoint_handle, UDI_USBXFER_ISO_WRITE,
			       portc->tmp_buf[pn], out_size)) < 0)
    {
      //cmn_err(CE_CONT, "oss usbaudio: Write transfer eror %d\n", err);
      return err;
    }

  return 0;
}

static int
feed_output (int dev, ossusb_devc * devc, ossusb_portc * portc)
{
  int pn, pos, err, len;
  oss_native_word flags;
  adev_p adev = audio_engines[dev];
  dmap_p dmap = adev->dmap_out;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->stopping)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return 0;
    }

  pn = portc->curr_datapipe;
  portc->curr_datapipe = (portc->curr_datapipe + 1) % NR_DATAPIPES;

  pos = (int) (dmap->byte_counter % dmap->bytes_in_use);
  len = portc->fragment_size;

  portc->overflow_samples += portc->overflow_size;
  if (portc->overflow_samples > 1000)
    {
      len += dmap->frame_size * (portc->overflow_samples / 1000);
      portc->overflow_samples = portc->overflow_samples % 1000;
    }

  output_convert (devc, portc, dmap, dmap->dmabuf, pos, pn, len);
  oss_audio_inc_byte_counter (dmap, len);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static void
start_input (int dev, ossusb_devc * devc, ossusb_portc * portc)
{
  int frag, err;
  oss_native_word flags;
  adev_p adev = audio_engines[dev];
  dmap_p dmap = adev->dmap_in;

  if (portc->stopping)
    return;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  frag = 0;

  if ((err =
       udi_usb_submit_request (portc->datapipe[0], rec_callback, portc,
			       portc->endpoint_handle, UDI_USBXFER_ISO_READ,
			       dmap->dmabuf + frag * portc->fragment_size,
			       portc->fragment_size)) < 0)
    {
      cmn_err (CE_WARN, "oss usbaudio: Read transfer error %d\n", err);
      cmn_err (CE_CONT, "Endpoint %02x\n",
	       udi_endpoint_get_num (portc->endpoint_handle));
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static void
usbaudio_trigger (int dev, int state)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  ossusb_portc *portc = audio_engines[dev]->portc;

  if (devc->disabled)
    return;

  if (portc->open_mode & PCM_ENABLE_OUTPUT)
    {
      if ((portc->prepared_modes & PCM_ENABLE_OUTPUT)
	  && (state & PCM_ENABLE_OUTPUT))
	{
	  portc->prepared_modes &= ~PCM_ENABLE_OUTPUT;
	  portc->curr_datapipe = 0;
	  portc->stopping = 0;

	  feed_output (dev, devc, portc);
	  feed_output (dev, devc, portc);
	}
      else if (!(state & PCM_ENABLE_OUTPUT))
	{
	  portc->stopping = 1;
#if 1
	  udi_usb_cancel_request (portc->datapipe[0]);
	  udi_usb_cancel_request (portc->datapipe[1]);
#endif
	  portc->curr_datapipe = 0;
	}
    }

  if (portc->open_mode & PCM_ENABLE_INPUT)
    {
      if ((portc->prepared_modes & PCM_ENABLE_INPUT)
	  && (state & PCM_ENABLE_INPUT))
	{
	  portc->prepared_modes &= ~PCM_ENABLE_INPUT;
	  portc->stopping = 0;
	  start_input (dev, devc, portc);
	}
      else if (!(state & PCM_ENABLE_INPUT))
	{
	  portc->stopping = 1;
#if 0
	  udi_usb_cancel_request (portc->datapipe[0]);
	  udi_usb_cancel_request (portc->datapipe[1]);
#endif
	  portc->curr_datapipe = 0;
	}
    }
}

static void
usbaudio_reset (int dev)
{
  usbaudio_trigger (dev, 0);
}

static int
usbaudio_prepare_for_input (int dev, int bsize, int bcount)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  ossusb_portc *portc = audio_engines[dev]->portc;
  adev_p adev = audio_engines[dev];

  if (devc->disabled)
    return -EPIPE;


  if (adev->flags & ADEV_NOINPUT)
    return -ENOTSUP;

  portc->stopping = 0;

  if (write_control_value
      (devc, portc->endpoint_handle, SAMPLING_FREQ_CONTROL, 3,
       portc->speed) < 0)
    {
      cmn_err (CE_CONT, "Failed to set %d Hz sampling rate\n", portc->speed);
      return -EIO;
    }

  /*
   * Handle fractional samples that don't fit in the 1ms period.
   */
  portc->overflow_size = portc->speed % 1000;
  portc->overflow_samples = 0;

  portc->prepared_modes |= PCM_ENABLE_INPUT;
  return 0;
}

static int
usbaudio_prepare_for_output (int dev, int bsize, int bcount)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  ossusb_portc *portc = audio_engines[dev]->portc;
  adev_p adev = audio_engines[dev];

  if (devc->disabled)
    return -EPIPE;

  if (adev->flags & ADEV_NOOUTPUT)
    return -ENOTSUP;

  portc->stopping = 0;

  if (write_control_value
      (devc, portc->endpoint_handle, SAMPLING_FREQ_CONTROL, 3,
       portc->speed) < 0)
    {
      cmn_err (CE_CONT, "Failed to set %d Hz sampling rate\n", portc->speed);
      return -EIO;
    }

  /*
   * Handle fractional samples that don't fit in the 1ms period.
   */
  portc->overflow_size = portc->speed % 1000;
  portc->overflow_samples = 0;

  portc->prepared_modes |= PCM_ENABLE_OUTPUT;
  return 0;
}

static int
usbaudio_check_input (int dev)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  if (devc->disabled)
    {
      cmn_err (CE_CONT,
	       "oss usbaudio: Audio device %d removed from the system.\n",
	       dev);
      return -EPIPE;
    }

  cmn_err (CE_CONT, "oss usbaudio: Audio input timed out on device %d.\n",
	   dev);
  return -EIO;
}

static int
usbaudio_check_output (int dev)
{
  ossusb_devc *devc = audio_engines[dev]->devc;
  if (devc->disabled)
    {
      cmn_err (CE_CONT,
	       "oss usbaudio: Audio device %d removed from the system.\n",
	       dev);
      return -EPIPE;
    }

  cmn_err (CE_CONT, "oss usbaudio: Audio output timed out on device %d.\n",
	   dev);
  return -EIO;
}

static int
usbaudio_local_qlen (int dev)
{
  ossusb_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_out;
  int delay = portc->pipeline_delay + 1;	/* Pipeline delay in 1 msec ticks */

  delay = delay * dmap->data_rate / 1000;	/* Bytes/msec */

  return delay;
}

static audiodrv_t usbaudio_driver = {
  usbaudio_open,
  usbaudio_close,
  usbaudio_output_block,
  usbaudio_start_input,
  usbaudio_ioctl,
  usbaudio_prepare_for_input,
  usbaudio_prepare_for_output,
  usbaudio_reset,
  usbaudio_local_qlen,
  NULL,
  NULL,
  NULL,
  usbaudio_trigger,
  usbaudio_set_rate,
  usbaudio_set_format,
  usbaudio_set_channels,
  NULL,
  NULL,
  usbaudio_check_input,
  usbaudio_check_output,
  NULL,				/* usbaudio_alloc_buffer */
  NULL,				/* usbaudio_free_buffer */
  NULL,
  NULL,
  NULL				/* usbaudio_get_buffer_pointer */
};

ossusb_devc *
ossusb_init_audiostream (ossusb_devc * devc, udi_usb_devc * usbdev, int inum,
			 int reinit)
{
  int nsettings, actsetting = 0, desc_len;
  unsigned char *desc, *d;
  adev_p adev;
  int i, old, p, l;
  void *endpoint_desc;

  char dev_name[128];

  int opts = ADEV_AUTOMODE;

  ossusb_portc *portc;

  if (devc->num_audio_engines >= MAX_PORTC)
    {
      cmn_err (CE_CONT, "usbaudio: Too many audio streaming interfaces\n");
      return devc;
    }

  if (usbdev == NULL)
    {
      cmn_err (CE_CONT, "usbaudio: usbdev==NULL\n");
      return devc;
    }

  nsettings = udi_usbdev_get_num_altsettings (usbdev);
  desc = udi_usbdev_get_endpoint (usbdev, 1, 0, &l);
  if (desc != NULL)
    {
      /* cmn_err(CE_CONT, "Endpoint: ");ossusb_dump_desc(desc, l); */
      endpoint_desc = desc;
    }
  else
    endpoint_desc = NULL;

  old = 0;

  if (reinit)
    for (i = 0; i < devc->num_audio_engines; i++)
      if (devc->portc[i].orig_endpoint_desc == endpoint_desc)	/* Already registered this */
	{
	  prepare_altsetting (devc, &devc->portc[i]);
	  return devc;
	}

  portc = &devc->portc[devc->num_audio_engines];
  portc->if_number = inum;
  portc->endpoint_desc = portc->orig_endpoint_desc = endpoint_desc;
  portc->usbdev = usbdev;
  portc->act_setting = -1;
  devc->num_audio_engines++;

  memset (dev_name, 0, sizeof (dev_name));
  strncpy (dev_name, devc->dev_name, sizeof (dev_name) - 1);
  portc->num_settings = nsettings;

  if (devc->num_settings <= 0)
    devc->num_settings = nsettings;
  if (devc->num_settings < nsettings)
    devc->num_settings = nsettings;

#if 1
  if (usb_trace > 2)
    for (i = 0; i < nsettings; i++)
      {
	desc = udi_usbdev_get_altsetting (usbdev, i, &desc_len);
	cmn_err (CE_CONT, "\nDumping altsetting %d (l=%d)\n", i, desc_len);
	if (usb_trace)
	  ossusb_dump_desc (desc, desc_len);
      }
#endif

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

  if (desc == NULL || desc_len < 1)
    for (i = 0; i < nsettings && (desc == NULL || desc_len < 1); i++)
      {
	UDB (cmn_err (CE_CONT, "Trying to read altsetting %d\n", i));
	desc = udi_usbdev_get_altsetting (usbdev, i, &desc_len);
	if (desc != NULL)
	  actsetting = i;
      }

  UDB (cmn_err (CE_CONT, "Altsetting %d, len %d\n", actsetting, desc_len));

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

  if (usb_trace > 2)
    ossusb_dump_desc (desc, desc_len);

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

      if (d[1] != CS_INTERFACE)
	{
	UDB (cmn_err (CE_CONT, "Unknown descriptor type %02x\n", d[1]))}
      else
	switch (d[2])
	  {
	  case AS_GENERAL:
	    portc->terminal_link = d[3];
	    break;
	  }

      p += l;
    }

  if (portc->terminal_link > 0 && portc->terminal_link <= devc->nunits)
    {
      char *s = dev_name + strlen (dev_name);

      sprintf (s, " %s", devc->units[portc->terminal_link].name);
      s += strlen (s);

      if (devc->units[portc->terminal_link].typ == TY_OUTPUT)
	{
	  sprintf (s, " input");
	  opts |= ADEV_NOOUTPUT;
	}
      else
	{
	  sprintf (s, " output");
	  opts |= ADEV_NOINPUT;
	}
    }

  if ((portc->audio_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
						devc->osdev,
						devc->osdev,
						dev_name,
						&usbaudio_driver,
						sizeof (audiodrv_t),
						opts,
						AFMT_S16_NE, devc, -1)) < 0)
    {
      return devc;
    }

  portc->devc = devc;
  adev = audio_engines[portc->audio_dev];
  adev->portc = portc;
  adev->mixer_dev = devc->mixer_dev;
  adev->rate_source = devc->portc[0].audio_dev;
  adev->max_block = 256;
  adev->min_fragments = 4;	/* vmix needs this */

  /*
   * Se the smart attach flag for vmix. In this way vmix will wait until
   * there is some input device available before actually attaching the
   * device. Otherwise vmix will pick the first (output) device before the
   * input master device gets created.
   */
  adev->vmix_flags |= VMIX_SMART_ATTACH;

  prepare_altsetting (devc, portc);

  if (devc->modeselect_ctl > 0)
    {
      oss_mixext *ext;

      ext = mixer_find_ext (devc->mixer_dev, devc->modeselect_ctl);
      ext->maxvalue = portc->num_settings;
    }

  return devc;
}

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

  if (cmd == SNDCTL_MIX_READ)
    return devc->act_setting;

  if (value >= devc->num_settings)
    value = devc->num_settings - 1;

  if (devc->act_setting != value)
    {
      int i;

      devc->act_setting = value;

      for (i = 0; i < devc->num_audio_engines; i++)
	prepare_altsetting (devc, &devc->portc[i]);

    }
  return value;
}
