/*
 * Purpose: Driver for Creative SB Live/Audigy/2/4. Audio, MIDI and mixer services.
 */
/*
 * 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
 */


#include "oss_sblive_cfg.h"
#include "midi_core.h"
#include "oss_pci.h"
#include "remux.h"

/*
 * Include the DSP files for emu10k1 (Live!) and emu10k2 (Audigy)
 * These headers have been generated using the emu10k tools (by 4Front).
 * The _be.h files have been generated in a big endian system and the others
 * in a litle endian (intel) system.
 */
#ifdef OSS_BIG_ENDIAN
#   include "emu10k1_dsp_be.h"
#   include "emu10k2_dsp_be.h"
#else
#   include "emu10k1_dsp.h"
#   include "emu10k2_dsp.h"
#endif

#define NO_EMU10K1_SYNTH
#undef  TEST_3D

#define BOGUS_MIXER_CONTROLS ( \
	SOUND_MASK_SPEAKER | \
	SOUND_MASK_ALTPCM | \
	SOUND_MASK_VIDEO | \
	SOUND_MASK_DEPTH | \
	SOUND_MASK_MONO \
	)

#ifndef linux
#define NO_EMU10K1_SYNTH
#endif

#include "ac97.h"
#include "sblive.h"
#include "eq1.h"

#define MAX_SENDS	4

#define SEND_L			0
#define SEND_R			1
#define SEND_SL			2
#define SEND_SR			3
#define SEND_C			4
#define SEND_W			5
#define SEND_RL			6
#define SEND_RR			7

#define SPDIF_L			20
#define SPDIF_R			21

static unsigned char default_routing[MAX_SENDS] =
  { SEND_L, SEND_R, SEND_SL, SEND_SR };
static unsigned char front_routing[MAX_SENDS] =
  { SEND_L, SEND_R, 0x3f, 0x3f };
static unsigned char surr_routing[MAX_SENDS] =
  { SEND_SL, SEND_SR, 0x3f, 0x3f };
static unsigned char center_lfe_routing[MAX_SENDS] =
  { SEND_C, SEND_W, 0x3f, 0x3f };
static unsigned char rear_routing[MAX_SENDS] =
  { SEND_RL, SEND_RR, 0x3f, 0x3f };
static unsigned char spdif_routing[MAX_SENDS] =
  { SPDIF_L, SPDIF_R, 0x3f, 0x3f };

typedef struct
{
  int speed;
  int pitch;
  int recbits;
  int audigy_recbits;
  int rom;
}
speed_ent;

/* Note! with audigy speedsel=7 means 12 kHz */

static speed_ent speed_tab[] = {
  {8000, 0xb6a41b, 7, 8, ROM7},
  {11025, 0xbe0b64, 6, 6, ROM6},
  {16000, 0xc6a41b, 5, 5, ROM5},
  {22050, 0xce0b64, 4, 3, ROM4},
  {24000, 0xd00000, 3, 3, ROM3},
  {32000, 0xd6a41b, 2, 2, ROM2},
  {44100, 0xde0b64, 1, 1, ROM1},
  {48000, 0xe00000, 0, 0, ROM0},
  {0}
};

#define PCI_VENDOR_ID_CREATIVE 		0x1102
#define PCI_DEVICE_ID_SBLIVE		0x0002
#define PCI_DEVICE_ID_AUDIGY		0x0004
#define PCI_DEVICE_ID_AUDIGYVALUE	0x0008
#define PCI_DEVICE_ID_AUDIGY_CARDBUS	0x2001

#define LEFT_CH		0
#define RIGHT_CH	1

void sblive_init_voice (sblive_devc * devc, int chn);
static void audigyuartintr (sblive_devc * devc);

static int
ac97_read (void *devc_, int wAddr)
{
  sblive_devc *devc = devc_;
  int dtemp = 0, i;

  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTB (devc->osdev, wAddr, devc->base + 0x1e);
  for (i = 0; i < 10000; i++)
    if (INB (devc->osdev, devc->base + 0x1e) & 0x80)
      break;
  if (i == 1000)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;		/* Timeout */
    }
  dtemp = INW (devc->osdev, devc->base + 0x1c);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return dtemp & 0xffff;
}

static int
ac97_write (void *devc_, int wAddr, int wData)
{
  sblive_devc *devc = devc_;
  oss_native_word flags;
  int i;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTB (devc->osdev, wAddr, devc->base + 0x1e);
  for (i = 0; i < 10000; i++)
    if (INB (devc->osdev, devc->base + 0x1e) & 0x80)
      break;
  OUTW (devc->osdev, wData, devc->base + 0x1c);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return 0;
}

unsigned int
sblive_read_reg (sblive_devc * devc, int reg, int chn)
{
  oss_native_word flags;
  unsigned int ptr, ptr_addr_mask, val, mask, size, offset;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  ptr_addr_mask = (devc->feature_mask & SB_AUDIGY) ? 0x0fff0000 : 0x07ff0000;
  ptr = ((reg << 16) & ptr_addr_mask) | (chn & 0x3f);
  OUTL (devc->osdev, ptr, devc->base + 0x00);	/* Pointer */
  val = INL (devc->osdev, devc->base + 0x04);	/* Data */
  if (reg & 0xff000000)
    {
      size = (reg >> 24) & 0x3f;
      offset = (reg >> 16) & 0x1f;
      mask = ((1 << size) - 1) << offset;
      val &= mask;
      val >>= offset;
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return val;
}

void
sblive_write_reg (sblive_devc * devc, int reg, int chn, unsigned int value)
{
  oss_native_word flags;
  unsigned int ptr, ptr_addr_mask, mask, size, offset;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  ptr_addr_mask = (devc->feature_mask & SB_AUDIGY) ? 0x0fff0000 : 0x07ff0000;
  ptr = ((reg << 16) & ptr_addr_mask) | (chn & 0x3f);
  OUTL (devc->osdev, ptr, devc->base + 0x00);	/* Pointer */
  if (reg & 0xff000000)
    {
      size = (reg >> 24) & 0x3f;
      offset = (reg >> 16) & 0x1f;
      mask = ((1 << size) - 1) << offset;
      value <<= offset;
      value &= mask;
      value |= INL (devc->osdev, devc->base + 0x04) & ~mask;	/* data */
    }
  OUTL (devc->osdev, value, devc->base + 0x04);	/* Data */
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
}

static void
write_efx (sblive_devc * devc, int reg, unsigned int value)
{
  sblive_write_reg (devc, reg, 0, value);
}

static void
update_vu (sblive_devc * devc, sblive_portc * portc, dmap_p dmap, int frag)
{
  int left = 0, right = 0;
  int i, l;

  frag %= dmap->nfrags;
  l = dmap->fragment_size / 2;

  if (portc->format == AFMT_AC3)	/* Raw S/PDIF mode */
    {
      if (devc->vu_tmp2 == 0)
	devc->vu_tmp2 = 2;

      portc->vu_left = devc->vu_tmp;
      portc->vu_right = 144 - devc->vu_tmp;
      devc->vu_tmp = devc->vu_tmp + devc->vu_tmp2;

      if (devc->vu_tmp >= 144)
	{
	  devc->vu_tmp2 = -2;
	  devc->vu_tmp = 144;
	}

      if (devc->vu_tmp <= 0)
	{
	  devc->vu_tmp2 = 2;
	  devc->vu_tmp = 0;
	}

      return;
    }

  if (portc->format == AFMT_U8)
    {
      unsigned char *p;
      int v;

      p = dmap->dmabuf + (frag * dmap->fragment_size);

      if (dmap->dmabuf != NULL)
	for (i = 0; i < l; i++)
	  {
	    v = *p;
	    p++;
	    v -= 128;
	    if (v < 0)
	      v = -v;
	    if (v > left)
	      left = v;

	    v = *p;
	    p++;
	    v -= 128;
	    if (v < 0)
	      v = -v;
	    if (v > right)
	      right = v;
	  }
    }
  else
    {
      short *p;
      int v;

      l /= 2;
      p = (short *) (dmap->dmabuf + (frag * dmap->fragment_size));

      if (dmap->dmabuf != NULL)
	for (i = 0; i < l; i++)
	  {
	    v = SSWAP (*p++) >> 8;
	    if (v < 0)
	      v = -v;
	    if (v > left)
	      left = v;

	    v = *p++ >> 8;
	    if (v < 0)
	      v = -v;
	    if (v > right)
	      right = v;
	  }
    }

  if (portc->channels == 1)	/* Mono */
    {
      if (right > left)
	left = right;
      right = left;
    }

  if (left > portc->vu_left)
    portc->vu_left = left;
  if (right > portc->vu_right)
    portc->vu_right = right;
}

static int
sbliveintr (oss_device_t * osdev)
{
  int p;
  unsigned int status;
#ifndef NO_EMU10K1_SYNTH
  extern int sblive_synth_enable;
#endif

  sblive_devc *devc = osdev->devc;

  /*
   * TODO: Fix mutexes and move the inputintr/outputintr calls outside the
   * mutex block.
   */
  /* oss_native_word flags; */
  /* MUTEX_ENTER (devc->mutex, flags); */

  status = INL (devc->osdev, devc->base + 0x08);

#if !defined(sparc)
  if (status == 0)
    {
      /* MUTEX_EXIT (devc->mutex, flags); */
      return 0;
    }
#endif

  if (status & 0x00000080)	/* MIDI RX interrupt */
    {
      if (devc->feature_mask & SB_AUDIGY)
	audigyuartintr (devc);
      else
	uart401_irq (&devc->uart401devc);
    }

  if (status & 0x00008000)	/* ADC buffer full intr */
    {
      /* Force the starting position to match this moment */
      unsigned int pos;
      pos = (INL (devc->osdev, devc->base + 0x10) >> 6) & 0xfffff;
      devc->portc[0].rec_starttime = pos;
    }

  if (status & 0x00000240)	/* Interval timer or channel loop interrupt */
    {
      for (p = 0; p < devc->n_audiodevs; p++)
	{
	  sblive_portc *portc = &devc->portc[p];

	  if (portc->audiodev >= 0 && portc->audiodev < num_audio_engines
	      && portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    {
	      int pos, n;
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;

	      pos = sblive_read_reg (devc, QKBCA, portc->voice_chn) & 0x00ffffff;	/* Curr pos */
	      pos <<= portc->out_sz;
	      pos -= dmap->driver_use_value;

	      if (dmap->fragment_size == 0)
		{
		  cmn_err (CE_WARN, "dmap->fragment_size == 0\n");
		  continue;
		}

	      pos /= dmap->fragment_size;
	      if (pos < 0 || pos >= dmap->nfrags)
		pos = 0;

	      /* 
	       * If this was a full/half loop interrupt then use forced pointer
	       */
	      if (sblive_get_voice_loopintr (devc, portc->voice_chn))
		pos = 0;	/* Full loop boundary */
	      else if (sblive_get_voice_halfloopintr (devc, portc->voice_chn))
		pos = dmap->nfrags / 2;	/* Half loop boundary */

	      n = 0;
	      while (dmap_get_qhead (dmap) != pos && n++ < dmap->nfrags)
		{
		  update_vu (devc, portc, dmap, dmap_get_qhead (dmap));
		  oss_audio_outputintr (portc->audiodev, 0);
		}
	    }

	  if (num_audio_engines > 0 && portc->audiodev < num_audio_engines
	      && portc->trigger_bits & PCM_ENABLE_INPUT)
	    {
	      int n;
	      unsigned int pos;

	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in;

	      /* Compute current pos based on the wall clock register */
	      pos = (INL (devc->osdev, devc->base + 0x10) >> 6) & 0xfffff;
	      if (pos > portc->rec_starttime)
		pos = pos - portc->rec_starttime;
	      else
		pos = 0xfffff - (portc->rec_starttime - pos);
	      pos = (pos * (portc->speed / 25)) / (48000 / 25);
	      pos *= 2;		/* 16 bit->bytes */
	      pos *= portc->channels;
	      pos = pos % dmap->bytes_in_use;
	      if (dmap->fragment_size == 0)
		cmn_err (CE_WARN, "dmap->fragment_size==0\n");
	      else
		{
		  pos /= dmap->fragment_size;
		  if (pos >= dmap->nfrags)
		    pos = 0;
		  n = 0;
		  while (dmap_get_qtail (dmap) != pos && n++ < dmap->nfrags)
		    {
		      oss_audio_inputintr (devc->recording_dev, 0);
		    }
		}
	    }
	}

#ifndef NO_EMU10K1_SYNTH
      if (sblive_synth_enable)
	sblive_synth_interrupt (devc);
#endif
    }

  OUTL (devc->osdev, status, devc->base + 0x08);	/* Acknowledge them */

  /* MUTEX_EXIT (devc->mutex, flags); */

  return 1;
}

static int
setup_passthrough (sblive_devc * devc, sblive_portc * portc, int pass)
{
  int ctrl = devc->passthrough_gpr;

  if (ctrl < 0)
    return 0;

  if (pass == portc->uses_spdif)
    return 1;

  if (pass && devc->spdif_busy)
    return 0;

  portc->uses_spdif = pass;

  sblive_write_reg (devc, ctrl + GPR0, 0, pass);
  sblive_write_reg (devc, ctrl + GPR0 + 1, 0, !pass);
  if (pass)
    {
      devc->spdif_busy = 1;
    }
  else
    {
      devc->spdif_busy = 0;
    }

  return 1;
}

static int
sblive_audio_set_rate (int dev, int arg)
{
  sblive_portc *portc = audio_engines[dev]->portc;
  int i, n = -1, dif, best = -1, bestdif = 0x7fffffff;

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

  for (i = 0; speed_tab[i].speed != 0 && n == -1; i++)
    {
      if (speed_tab[i].speed == arg)	/* Exact match */
	{
	  n = i;
	  break;
	}

      dif = arg - speed_tab[i].speed;
      if (dif < 0)
	dif = -dif;

      if (dif < bestdif)
	{
	  best = i;
	  bestdif = dif;
	}
    }

  if (n == -1)
    n = best;

  if (n == -1)
    n = 0;

  portc->speed = speed_tab[n].speed;
  portc->speedsel = n;

  return portc->speed;
}

static short
sblive_audio_set_channels (int dev, short arg)
{
  sblive_portc *portc = audio_engines[dev]->portc;

  if ((arg != 1) && (arg != 2))
    return portc->channels;
  portc->channels = arg;

  return portc->channels;
}

static unsigned int
sblive_audio_set_format (int dev, unsigned int arg)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;

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

  if (portc->mode & OPEN_READ)
    arg = AFMT_S16_LE;

  if (arg != AFMT_U8 && arg != AFMT_S16_LE && arg != AFMT_AC3)
    return portc->format;

  /* Enforce stereo mode with AC3 */
  if (arg == AFMT_AC3)
    {
      if (!setup_passthrough (devc, portc, 1))
	return portc->format;
      portc->channels = 2;
      portc->speed = 48000;
    }
  else
    if (portc->input_type != ITYPE_SPDIF && portc->uses_spdif
	&& arg != AFMT_AC3)
    {
      setup_passthrough (devc, portc, 0);
    }

  portc->format = arg;

  return portc->format;
}

static int mixer_ext_init (int dev);
static int create_efx_mixer (int dev);
static int sblive_set_gpr (int dev, int ctrl, unsigned int cmd, int value);

static int
is_special_gpr (int gpr)
{
  if (gpr >= NEXT_FREE_GPR)
    return 0;

  if (SPECIAL_GPRS & (1 << gpr))
    {
      return 1;
    }

  if (gpr > 0)
    if (SPECIAL_GPRS & (1 << (gpr - 1)))
      {
	return 1;
      }
  return 0;
}

static void update_output_device (sblive_devc * devc, sblive_portc * portc);

static int
sblive_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;
  sblive_reg *reg = (sblive_reg *) arg;
  gpr_info *gpr = (gpr_info *) arg;
  const_info *consts = (const_info *) arg;
  unsigned int *code = (unsigned int *) arg;
  int i;

  switch (cmd)
    {
    case SNDCTL_DSP_GETCHANNELMASK:
      return *arg =
	DSP_BIND_FRONT | DSP_BIND_REAR | DSP_BIND_SURR | DSP_BIND_CENTER_LFE;
      break;

    case SNDCTL_DSP_BIND_CHANNEL:
      {
	int val;

	val = *arg;
	portc->speaker_mode = SMODE_BIND;
	portc->binding = val;
	return *arg = val;
      }
      break;

    case SNDCTL_DSP_SETPLAYVOL:
      {
	int left, right, val;

	val = *arg;

	left = val & 0xff;
	right = (val >> 8) & 0xff;
	if (left < 0)
	  left = 0;
	if (right < 0)
	  right = 0;
	if (left > 100)
	  left = 100;
	if (right > 100)
	  right = 100;

#if 0
	if (right > left)
	  left = right;
#endif
	portc->playvol = left;
	update_output_device (devc, portc);

	return *arg = left | (left << 8);
      }
      break;

    case SNDCTL_DSP_GETPLAYVOL:
      {
	int vol;
	vol = (portc->playvol << 8) | portc->playvol;
	return *arg = vol;
      }
      break;

    case SBLIVE_READREG:
#ifdef GET_PROCESS_UID
      if (GET_PROCESS_UID () != 0)	/* Not root */
	return OSS_EINVAL;
#endif
      reg->value = sblive_read_reg (devc, reg->reg, reg->chn);
      return 0;
      break;

    case SBLIVE_WRITEREG:
#ifdef GET_PROCESS_UID
      if (GET_PROCESS_UID () != 0)	/* Not root */
	return OSS_EINVAL;
#endif
      sblive_write_reg (devc, reg->reg, reg->chn, reg->value);
      return 0;
      break;

    case SBLIVE_READGPIO:
#ifdef GET_PROCESS_UID
      if (GET_PROCESS_UID () != 0)	/* Not root */
	return OSS_EINVAL;
#endif
      return *arg = INW (devc->osdev, devc->base + 0x18);
      break;

    case SBLIVE_WRITEGPIO:
      {
	int val;
	val = *arg;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	OUTW (devc->osdev, val, devc->base + 0x18);

      }
      return 0;

    case SBLIVE_WRITEPARMS:
      {

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	if (gpr->ngpr >= MAX_GPR_PARMS)
	  return OSS_EIO;

	for (i = 0; i < gpr->ngpr; i++)
	  {
	    gpr->gpr[i].name[GPR_NAME_SIZE - 1] = 0;	/* Overflow protection */
	    if (strlen (gpr->gpr[i].name) >= 32)	/* Name may be bad */
	      {
		return OSS_EIO;
	      }

	    if (gpr->gpr[i].num >= MAX_GPR)
	      {
		return OSS_EIO;
	      }

/* cmn_err(CE_CONT, "Gpr %d = %s (vol %x) type=%x\n", gpr->gpr[i].num, gpr->gpr[i].name, gpr->gpr[i].def, gpr->gpr[i].type); */
	    if (gpr->gpr[i].type != MIXT_GROUP)
	      {
		if (is_special_gpr (gpr->gpr[i].num))
		  sblive_set_gpr (devc->mixer_dev, gpr->gpr[i].num,
				  SNDCTL_MIX_WRITE,
				  devc->gpr_values[gpr->gpr[i].num]);
		else
		  sblive_set_gpr (devc->mixer_dev, gpr->gpr[i].num,
				  SNDCTL_MIX_WRITE, gpr->gpr[i].def);
	      }
	  }


	if (devc->gpr == NULL)
	  {
	    devc->gpr = PMALLOC (devc->osdev, sizeof (gpr_info));
	    if (devc->gpr == NULL)
	      {
		cmn_err (CE_WARN, "Out of memory (gpr)\n");
		return OSS_ENOSPC;
	      }
	    memset (devc->gpr, 0, sizeof (gpr_info));
	  }
	memcpy (devc->gpr, gpr, sizeof (gpr_info));
	create_efx_mixer (devc->mixer_dev);
      }
      return 0;
      break;

    case SBLIVE_WRITECONST:
      {

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	if (consts->nconst >= MAX_CONST_PARMS)
	  return OSS_EIO;

	for (i = 0; i < consts->nconst; i++)
	  {
	    if (consts->consts[i].gpr >= MAX_GPR)
	      {
		return OSS_EIO;
	      }

	    sblive_write_reg (devc, consts->consts[i].gpr + GPR0, 0,
			      consts->consts[i].value);
	  }

      }
      return 0;
      break;

    case SBLIVE_WRITECODE1:
      {
	int pc;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	for (pc = 0; pc < 512; pc++)
	  {
	    write_efx (devc, UC0 + pc, code[pc]);
	  }

      }
      sblive_write_reg (devc, DBG, 0, 0);
      return 0;
      break;

    case SBLIVE_WRITECODE2:
      {
	int pc;

#ifdef GET_PROCESS_UID
	if (GET_PROCESS_UID () != 0)	/* Not root */
	  return OSS_EINVAL;
#endif
	for (pc = 0; pc < 512; pc++)
	  {
	    write_efx (devc, UC0 + 512 + pc, code[pc]);
	  }
      }
      sblive_write_reg (devc, DBG, 0, 0);
      return 0;
      break;

    case SBLIVE_GETCHIPTYPE:
#ifdef GET_PROCESS_UID
      if (GET_PROCESS_UID () != 0)	/* Not root */
	return OSS_EINVAL;
#endif
      return *arg = devc->feature_mask & ~SB_AUDIGY2;
      break;

    }
  return OSS_EINVAL;
}

static void sblive_audio_trigger (int dev, int state);

static void
sblive_audio_reset (int dev)
{
  sblive_audio_trigger (dev, 0);
}

static void
sblive_audio_reset_input (int dev)
{
  sblive_portc *portc = audio_engines[dev]->portc;
  sblive_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
sblive_audio_reset_output (int dev)
{
  sblive_portc *portc = audio_engines[dev]->portc;
  sblive_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

static int sblive_set_vol (int dev, int ctrl, unsigned int cmd, int value);

static void
reset_portc_volume (sblive_devc * devc, sblive_portc * portc)
{
  int v;
#ifdef TEST_3D
  v = 100 | (50 << 8) | (0 << 16);	/* vol=100, dist=50, angle=0 */
#else
  v = 100 | (100 < 8);
#endif
  sblive_set_vol (devc->mixer_dev, portc->port_number, SNDCTL_MIX_WRITE, v);
}

#if MAX_ADEV == 2
static const unsigned int binding_map[MAX_ADEV] =
  { DSP_BIND_FRONT, DSP_BIND_SURR };
#else
static const unsigned int binding_map[MAX_ADEV] = {
  DSP_BIND_FRONT,
  DSP_BIND_FRONT,
  DSP_BIND_SURR,
  DSP_BIND_CENTER_LFE,
  DSP_BIND_REAR
};
#endif

/*ARGSUSED*/
static int
sblive_audio_open (int dev, int mode, int open_flags)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

  portc->mode = mode;
  portc->audio_active &= ~mode;

  portc->resetvol = 0;

  portc->speaker_mode = devc->speaker_mode;
  portc->binding = binding_map[portc->port_number];
  if (portc->binding == 0)
    portc->binding = DSP_BIND_FRONT;
  mixer_devs[devc->mixer_dev]->modify_counter++;	/* Force update of mixer */

  audio_engines[dev]->flags &= ~ADEV_FIXEDRATE;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  if (devc->autoreset || portc->resetvol)
    reset_portc_volume (devc, portc);

  if (portc->input_type == ITYPE_SPDIF)
    {
      if (!setup_passthrough (devc, portc, 1))
	{
	  portc->mode = 0;
	  return OSS_EBUSY;
	}
    }
  else
    {
      /* Enable AC3 format if possible */
      if (!devc->spdif_busy)
	audio_engines[dev]->oformat_mask |= AFMT_AC3;
      else
	audio_engines[dev]->oformat_mask &= ~AFMT_AC3;
    }
  return 0;
}

static void
sblive_audio_close (int dev, int mode)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;

  sblive_audio_reset (dev);
  portc->mode = 0;
  portc->audio_active &= ~mode;
  mixer_devs[devc->mixer_dev]->modify_counter++;
  setup_passthrough (devc, portc, 0);
  audio_engines[dev]->oformat_mask |= AFMT_AC3;
}

#ifdef TEST_3D
/* Sin table for Q1 (*10000) */
static const int sincos[] = {
  0, 174, 348, 523, 697, 871, 1045, 1218, 1391, 1564,
  1736, 1908, 2079, 2249, 2419, 2588, 2756, 2923, 3090, 3255,
  3420, 3583, 3746, 3907, 4067, 4226, 4383, 4539, 4694, 4848,
  5000, 5150, 5299, 5446, 5591, 5735, 5877, 6018, 6156, 6293,
  6427, 6560, 6691, 6819, 6946, 7071, 7193, 7313, 7431, 7547,
  7660, 7771, 7880, 7986, 8090, 8191, 8290, 8386, 8480, 8571,
  8660, 8746, 8829, 8910, 8987, 9063, 9135, 9205, 9271, 9335,
  9396, 9455, 9510, 9563, 9612, 9659, 9702, 9743, 9781, 9816,
  9848, 9876, 9902, 9925, 9945, 9961, 9975, 9986, 9993, 9998,
  10000,
};

static __inline__ int
oss_sin (int angle)
{
  int a;
  int f;

  a = angle % 90;

  if ((angle / 90) & 1)
    a = 90 - a;

  f = sincos[a];
  if (angle >= 180)
    f = -f;
  return f;
}

static __inline__ int
oss_cos (int angle)
{
  int a, q;
  int f;

  a = angle % 90;
  q = angle / 90;

  if (!(q & 1))
    a = 90 - a;

  f = sincos[a];
  if (angle >= 90 && angle < 270)
    f = -f;
  return f;
}

static void
compute_3d (sblive_devc * devc, sblive_portc * portc, int voice, int chn,
	    int *send)
{
  int angle = portc->playangle;
  int dist, opening = 45;
  int i;

  /* left, right, rear_right, rear_left */
  static int spk_angles[4] = { 315, 45, 135, 225 };
  int gain = 50, leak = 0;
  int v[4];

  dist = portc->playdist;

  if (dist < 0)
    dist = 0;
  if (dist > 100)
    dist = 100;
  portc->playdist = dist;

  dist = 100 - dist;		/* Invert distance */
  opening = (90 * dist) / 100;

  if (dist < 50)
    {				/* Attenuate distant sounds */
      gain = dist;
    }
  else
    {
      /* "Expand" close sounds by leaking signal to silent channels */
      leak = dist - 50;
    }

  if (portc->channels == 2)
    {
      if (chn == LEFT_CH)
	angle -= opening;
      else
	angle += opening;
    }

  if (angle < 0)
    angle += 360;

  angle %= 360;

  for (i = 0; i < 4; i++)
    v[i] = (gain * portc->playvol * 255 + 25) / 50;

  for (i = 0; i < 4; i++)
    {
      int a = spk_angles[i] - angle;

      if (a < 0)
	a = -a;			/* ABS */
      if (a > 180)
	a = 360 - a;

      if (a >= 90)		/* Too far */
	{
	  v[i] = 0;		/* Muted speaker */
	  continue;
	}
      else
	v[i] = ((v[i] * oss_cos (a) + 5000) / 10000) / 100;
    }

  if (leak > 0)
    {
      leak = (255 * portc->playvol * leak + 2500) / 5000;

      for (i = 0; i < 4; i++)
	if (v[i] < leak)
	  v[i] = leak;
    }

  send[0] = v[0];		/* Left */
  send[1] = v[1];		/* Right */
  send[2] = v[3];		/* Rear left */
  send[3] = v[2];		/* Rear right */
}
#endif

static void
write_routing (sblive_devc * devc, int voice, unsigned char *routing)
{
  int i;

  if (routing == NULL)
    routing = default_routing;

  if (devc->feature_mask & SB_AUDIGY)
    {
      unsigned int srda = 0;

      for (i = 0; i < 4; i++)
	srda |= routing[i] << (i * 8);

      sblive_write_reg (devc, SRDA, voice, srda);
    }
  else
    {
      int fxrt = 0;

      for (i = 0; i < 4; i++)
	fxrt |= routing[i] << ((i * 4) + 16);
      sblive_write_reg (devc, FXRT, voice, fxrt);
    }
}

/*ARGSUSED*/
static void
compute_bind (sblive_devc * devc, sblive_portc * portc, unsigned char *send,
	      int chn)
{
  memset (send, 0, MAX_SENDS);

  if (chn == LEFT_CH)
    send[0] = (0xff * portc->playvol + 50) / 100;
  else
    send[1] = (0xff * portc->playvol + 50) / 100;

  switch (portc->binding)
    {
    case DSP_BIND_FRONT:
      portc->routing = front_routing;
      break;
    case DSP_BIND_SURR:
      portc->routing = surr_routing;
      break;
    case DSP_BIND_CENTER_LFE:
      portc->routing = center_lfe_routing;
      break;
    case DSP_BIND_REAR:
      portc->routing = rear_routing;
      break;
    default:
      portc->routing = default_routing;
    }
}

static void
update_output_volume (int dev, int voice, int chn)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;
  int left, right;
  unsigned int loop_start, loop_end, tmp;
  unsigned char send[MAX_SENDS];

  send[0] = 0xff;		/* Max */
  send[1] = 0xff;		/* Max */
  send[2] = 0xff;		/* Max */
  send[3] = 0xff;		/* Max */

  if (portc->input_type == ITYPE_SPDIF || portc->format == AFMT_AC3)
    {
      /* Digital voice */
      send[2] = 0;		/* Muted */
      send[3] = 0;		/* Muted */

      /* sends are revered between Audigy2 and Audigy */
      left = (devc->feature_mask & SB_AUDIGY2) ? 1 : 0;
      right = !left;

      if (portc->channels > 1)
	{
	  if (chn == LEFT_CH)
	    {
	      send[left] = 0;
	    }
	  else
	    {
	      send[right] = 0;
	    }
	}
    }
  else
    {
      /* Analog voice */

      if (portc->channels > 1)
	{
	  if (chn == LEFT_CH)
	    {
	      send[1] = 0;
	    }
	  else
	    {
	      send[0] = 0;
	    }
	}
      send[2] = send[0];
      send[3] = send[1];

#ifdef TEST_3D
      if (portc->speaker_mode == SMODE_3D)
	compute_3d (devc, portc, voice, chn, send);
      else
#endif
	{
	  send[0] = (send[0] * portc->playvol + 50) / 100;
	  send[1] = (send[1] * portc->playvol + 50) / 100;
	  send[2] = (send[2] * portc->playvol + 50) / 100;
	  send[3] = (send[3] * portc->playvol + 50) / 100;

	  switch (portc->speaker_mode)
	    {
	    case SMODE_FRONT:
	      send[2] = send[3] = 0;
	      break;

	    case SMODE_SURR:
	      send[0] = send[1] = 0;
	      break;

	    case SMODE_FRONTREAR:
	      break;

	    case SMODE_BIND:
	      compute_bind (devc, portc, send, chn);
	      break;
	    }
	}

      /* Analog voice */
    }

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  loop_end = sblive_read_reg (devc, SDL, voice) & 0xffffff;
  sblive_write_reg (devc, SDL, voice, loop_end | (send[3] << 24));
  loop_start = sblive_read_reg (devc, SCSA, voice) & 0xffffff;
  sblive_write_reg (devc, SCSA, voice, loop_start | (send[2] << 24));
  tmp = sblive_read_reg (devc, PTAB, voice) & 0xffff0000;
  sblive_write_reg (devc, PTAB, voice, tmp | (send[0] << 8) | send[1]);
  write_routing (devc, voice, portc->routing);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static void
setup_audio_voice (int dev, int voice, int chn)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  unsigned int nCRA = 0;

  unsigned int loop_start, loop_end;

  int sz = 1;
  int start_pos;

  sblive_write_reg (devc, VEDS, voice, 0x0);	/* OFF */
  sblive_write_reg (devc, VTFT, voice, 0xffff);
  sblive_write_reg (devc, CVCF, voice, 0xffff);

  if (devc->feature_mask & SB_AUDIGY)
    sblive_write_reg (devc, SRDA, voice, 0x03020100);
  else
    sblive_write_reg (devc, FXRT, voice, 0x32100000);


  sz =
    (((portc->format == AFMT_S16_LE
       || portc->format == AFMT_AC3)) ? 1 : 0) + ((portc->channels ==
						   2) ? 1 : 0);

  loop_start = dmap->driver_use_value >> sz;
  loop_end = (dmap->driver_use_value + dmap->bytes_in_use) >> sz;

  /* set mono/stereo */
  sblive_write_reg (devc, CPF, voice, (portc->channels > 1) ? 0x8000 : 0);

  nCRA = (portc->channels > 1) ? 28 : 30;
  nCRA *= (portc->format == AFMT_S16_LE || portc->format == AFMT_AC3) ? 1 : 2;
  start_pos = loop_start + nCRA;

  /* SDL, ST, CA */
  portc->out_sz = sz;

  sblive_write_reg (devc, SDL, voice, loop_end);
  sblive_write_reg (devc, SCSA, voice, loop_start);
  sblive_write_reg (devc, PTAB, voice, 0);

  update_output_volume (dev, voice, chn);	/* Set volume */

  if (portc->format == AFMT_S16_LE || portc->format == AFMT_AC3)
    sblive_write_reg (devc, QKBCA, voice, start_pos);
  else
    sblive_write_reg (devc, QKBCA, voice, start_pos | BYTESIZE);

  sblive_write_reg (devc, Z1, voice, 0);
  sblive_write_reg (devc, Z2, voice, 0);

  sblive_write_reg (devc, MAPA, voice, 0x1fff | (devc->silent_page_phys << 1));	/* This is really a physical address */
  sblive_write_reg (devc, MAPB, voice, 0x1fff | (devc->silent_page_phys << 1));	/* This is really a physical address */

  sblive_write_reg (devc, VTFT, voice, 0x0000ffff);
  sblive_write_reg (devc, CVCF, voice, 0x0000ffff);
  sblive_write_reg (devc, MEHA, voice, 0);
  sblive_write_reg (devc, MEDS, voice, 0x7f);
  sblive_write_reg (devc, MLV, voice, 0x8000);
  sblive_write_reg (devc, VLV, voice, 0x8000);
  sblive_write_reg (devc, VFM, voice, 0);
  sblive_write_reg (devc, TMFQ, voice, 0);
  sblive_write_reg (devc, VVFQ, voice, 0);
  sblive_write_reg (devc, MEV, voice, 0x8000);
  sblive_write_reg (devc, VEHA, voice, 0x7f7f);	/* OK */
  sblive_write_reg (devc, VEV, voice, 0x8000);	/* No volume envelope delay (OK) */
  sblive_write_reg (devc, PEFE_FILTERAMOUNT, voice, 0x7f);
  sblive_write_reg (devc, PEFE_PITCHAMOUNT, voice, 0x00);
}

/*ARGSUSED*/
static void
update_output_device (sblive_devc * devc, sblive_portc * portc)
{
  int voiceL = portc->voice_chn, voiceR = portc->voice_chn + 1;

  if (!(portc->audio_active & PCM_ENABLE_OUTPUT))
    return;

  update_output_volume (portc->audiodev, voiceL, LEFT_CH);
  update_output_volume (portc->audiodev, voiceR, RIGHT_CH);
}


/*ARGSUSED*/
static void
sblive_audio_output_block (int dev, oss_native_word buf, int count,
			   int fragsize, int intrflag)
{
  sblive_portc *portc = audio_engines[dev]->portc;

  portc->audio_active |= PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
}

/*ARGSUSED*/
static void
sblive_audio_start_input (int dev, oss_native_word buf, int count,
			  int fragsize, int intrflag)
{
  sblive_portc *portc = audio_engines[dev]->portc;

  portc->audio_active |= PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
}

void
sblive_set_loop_stop (sblive_devc * devc, int voice, int s)
{
  unsigned int tmp;
  int offs, bit;

  offs = voice / 32;
  bit = voice % 32;
  s = !!s;

  tmp = sblive_read_reg (devc, SOLL + offs, 0);
  tmp &= ~(1 << bit);

  if (s)
    tmp |= (1 << bit);
  sblive_write_reg (devc, SOLL + offs, 0, tmp);
}

int
sblive_get_voice_loopintr (sblive_devc * devc, int voice)
{
  unsigned int tmp;
  int offs, bit;

  offs = voice / 32;
  bit = voice % 32;

  tmp = sblive_read_reg (devc, CLIPL + offs, 0);
  tmp &= 1 << bit;
  sblive_write_reg (devc, CLIPL + offs, 0, tmp);	/* Ack the interrupt */

  return !!tmp;
}

int
sblive_get_voice_halfloopintr (sblive_devc * devc, int voice)
{
  unsigned int tmp;
  int offs, bit;

  offs = voice / 32;
  bit = voice % 32;

  tmp = sblive_read_reg (devc, HLIPL + offs, 0);
  tmp &= 1 << bit;
  sblive_write_reg (devc, HLIPL + offs, 0, tmp);	/* Ack the interrupt */

  return !!tmp;
}

void
sblive_set_voice_intr (sblive_devc * devc, int voice, int s)
{
  unsigned int tmp;
  int offs, bit;

  offs = voice / 32;
  bit = voice % 32;
  s = !!s;

  tmp = sblive_read_reg (devc, CLIEL + offs, 0);
  tmp &= ~(1 << bit);
  if (s)
    tmp |= (1 << bit);
  sblive_write_reg (devc, CLIEL + offs, 0, tmp);
  sblive_write_reg (devc, HLIEL + offs, 0, tmp);
}

static unsigned int
emu_rate_to_pitch (unsigned int rate)
{
  static unsigned int logMagTable[128] = {
    0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
    0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
    0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081,
    0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191,
    0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7,
    0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
    0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
    0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26,
    0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
    0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885,
    0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
    0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
    0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
    0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
    0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83,
    0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
  };
  static char logSlopeTable[128] = {
    0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
    0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
    0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
    0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
    0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
    0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
    0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
    0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
    0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
    0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
    0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
    0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
    0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
    0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
    0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
    0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
  };
  int i;

  if (rate == 0)
    return 0;			/* Bail out if no leading "1" */
  rate *= 11185;		/* Scale 48000 to 0x20002380 */
  for (i = 31; i > 0; i--)
    {
      if (rate & 0x80000000)
	{			/* Detect leading "1" */
	  return (((unsigned int) (i - 15) << 20) +
		  logMagTable[0x7f & (rate >> 24)] +
		  (0x7f & (rate >> 17)) * logSlopeTable[0x7f & (rate >> 24)]);
	}
      rate <<= 1;
    }

  return 0;			/* Should never reach this point */
}

static unsigned int
emu_rate_to_linearpitch (unsigned int rate)
{
  rate = (rate << 8) / 375;
  return (rate >> 1) + (rate & 1);
}

static void
start_audio_voice (int dev, int voice, int chn)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;
  unsigned int sample, initial_pitch, pitch_target;
  unsigned int cra, cs, ccis, i;

  /* setup CCR regs */
  cra = 64;
  cs = (portc->channels > 1) ? 4 : 2;
  ccis = (portc->channels > 1) ? 28 : 30;
  ccis *= (portc->format == AFMT_S16_LE || portc->format == AFMT_AC3) ? 1 : 2;
  sample = (portc->format == AFMT_S16_LE
	    || portc->format == AFMT_AC3) ? 0x00000000 : 0x80808080;

  for (i = 0; i < cs; i++)
    sblive_write_reg (devc, CD0 + i, voice, sample);

  sblive_write_reg (devc, CCR_CACHEINVALIDSIZE, voice, 0);
  sblive_write_reg (devc, CCR_READADDRESS, voice, cra);
  sblive_write_reg (devc, CCR_CACHEINVALIDSIZE, voice, ccis);

  /* Set current pitch */
  sblive_write_reg (devc, IFA, voice, 0xff00);
  sblive_write_reg (devc, VTFT, voice, 0xffffffff);
  sblive_write_reg (devc, CVCF, voice, 0xffffffff);
  sblive_set_loop_stop (devc, voice, 0);

  pitch_target = emu_rate_to_linearpitch (portc->speed);
  initial_pitch = emu_rate_to_pitch (portc->speed) >> 8;
  sblive_write_reg (devc, PTRX_PITCHTARGET, voice, pitch_target);
  sblive_write_reg (devc, CPF_CURRENTPITCH, voice, pitch_target);
  sblive_write_reg (devc, IP, voice, initial_pitch);

  if (chn == LEFT_CH)
    {
      sblive_get_voice_loopintr (devc, voice);
      sblive_get_voice_halfloopintr (devc, voice);
      sblive_set_voice_intr (devc, voice, 1);
    }
  sblive_write_reg (devc, VEDS, voice, /*0x80 | */ 0x7f7f);	/* Trigger (OK) */
}

/*ARGSUSED*/
static void
stop_audio_voice (int dev, int voice, int chn)
{
  sblive_devc *devc = audio_engines[dev]->devc;

  sblive_write_reg (devc, IFA, voice, 0xffff);
  sblive_write_reg (devc, VTFT, voice, 0xffff);
  sblive_write_reg (devc, PTRX_PITCHTARGET, voice, 0);
  sblive_write_reg (devc, CPF_CURRENTPITCH, voice, 0);
  sblive_write_reg (devc, IP, voice, 0);
  sblive_set_loop_stop (devc, voice, 1);
  sblive_set_voice_intr (devc, voice, 0);
}

static void
sblive_audio_trigger (int dev, int state)
{
  sblive_portc *portc = audio_engines[dev]->portc;
  sblive_devc *devc = audio_engines[dev]->devc;
  int voiceL = portc->voice_chn, voiceR = portc->voice_chn + 1;

  if (portc->mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  if ((portc->audio_active & PCM_ENABLE_OUTPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      start_audio_voice (dev, voiceL, LEFT_CH);
	      /* sblive_dump_regs(devc, voiceL); */
	      if (portc->channels > 1)
		start_audio_voice (dev, voiceR, RIGHT_CH);
	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	    }
	}
      else
	{
	  if ((portc->audio_active & PCM_ENABLE_OUTPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
	      portc->audio_active &= ~PCM_ENABLE_OUTPUT;
	      stop_audio_voice (dev, voiceL, LEFT_CH);
	      stop_audio_voice (dev, voiceR, RIGHT_CH);
	    }
	}
    }

  if (portc->mode & OPEN_READ && !(audio_engines[dev]->flags & ADEV_NOINPUT))
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_active & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      int tmp = sblive_read_reg (devc, ADCSR, 0);
	      unsigned int pos;

	      if (portc->input_type == ITYPE_SPDIF)
		{
		  /* Start recording from S/PDIF input A */
		  sblive_write_reg (devc, SPRC, 0, portc->in_szbits | 0x00);
		}
	      else
		{
		  if (devc->feature_mask & SB_AUDIGY)
		    {
		      tmp |= 0x10;	/* Left channel enable */
		      if (portc->channels > 1)
			tmp |= 0x20;	/* Right channel enable */
		    }
		  else
		    {
		      tmp |= 0x08;	/* Left channel enable */
		      if (portc->channels > 1)
			tmp |= 0x10;	/* Right channel enable */
		    }
		  sblive_write_reg (devc, ADCBS, 0, portc->in_szbits);
		  sblive_write_reg (devc, ADCSR, 0, tmp);	/* GO */
		}

	      pos = (INL (devc->osdev, devc->base + 0x10) >> 6) & 0xfffff;
	      portc->rec_starttime = pos;
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	    }
	}
      else
	{
	  if ((portc->audio_active & PCM_ENABLE_INPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      if (portc->input_type == ITYPE_SPDIF)
		sblive_write_reg (devc, SPRC, 0, 0);
	      else
		sblive_write_reg (devc, ADCSR, 0, 0);
	      portc->trigger_bits &= ~PCM_ENABLE_INPUT;
	      portc->audio_active &= ~PCM_ENABLE_INPUT;
	    }
	}
    }

}

/*ARGSUSED*/
static int
sblive_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  int sz = -1;

  if (audio_engines[dev]->flags & ADEV_NOINPUT)
    {
      cmn_err (CE_WARN, "Audio device %d is output only\n", dev);
      return OSS_EIO;
    }

  if (dmap->buffsize > 65536)
    {
      cmn_err (CE_WARN, "Recording buffer bigger than 64k\n");
      dmap->buffsize = 65536;
    }

#ifdef sun1
  if (dmap->buffsize == 36864)
    {
      dmap->buffsize = 32768;
    }
#endif

  switch (dmap->buffsize)
    {
    case 4096:
      sz = 15;
      break;
    case 8192:
      sz = 19;
      break;
    case 16384:
      sz = 23;
      break;
    case 32768:
      sz = 27;
      break;
    case 65536:
      sz = 31;
      break;

    default:
      cmn_err (CE_WARN, "Unsupported input buffer size %d\n", dmap->buffsize);
      return OSS_ENOSPC;
    }

  if (portc->input_type == ITYPE_SPDIF)
    {
      sblive_write_reg (devc, SPRA, 0, dmap->dmabuf_phys);
      sblive_write_reg (devc, SPRC, 0, 0);
    }
  else
    {
      sblive_write_reg (devc, ADCBA, 0, dmap->dmabuf_phys);
      sblive_write_reg (devc, ADCBS, 0, 0);
    }
  portc->in_szbits = sz;

  sblive_write_reg (devc, ADCSR, 0, 0x0);

  if (portc->input_type == ITYPE_ANALOG)
    {
      if (devc->feature_mask & SB_AUDIGY)
	sblive_write_reg (devc, ADCSR, 0,
			  speed_tab[portc->speedsel].audigy_recbits);
      else
	sblive_write_reg (devc, ADCSR, 0, speed_tab[portc->speedsel].recbits);
    }

  devc->recording_dev = dev;

  portc->audio_active |= PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;

  return 0;
}

/*ARGSUSED*/
static int
sblive_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;

  int voiceL = portc->voice_chn, voiceR = portc->voice_chn + 1;

  if (audio_engines[dev]->flags & ADEV_NOOUTPUT)
    return OSS_EIO;

  /* AC3 needs stereo too */
  if (portc->format == AFMT_AC3 || portc->input_type == ITYPE_SPDIF)
    {
      portc->channels = 2;
      portc->speed = 48000;
      portc->routing = spdif_routing;
    }
  else
    portc->routing = default_routing;

  /* Left channel */
  sblive_write_reg (devc, IFA, voiceL, 0xffff);	/* Intial filter cutoff and attenuation */
  sblive_write_reg (devc, VEDS, voiceL, 0x0);	/* Volume envelope decay and sustain */
  sblive_write_reg (devc, VTFT, voiceL, 0xffff);	/* Volume target and Filter cutoff target */
  sblive_write_reg (devc, PTAB, voiceL, 0x0);	/* Pitch target and sends A and B */
  /* The same for right channel */
  sblive_write_reg (devc, IFA, voiceR, 0xffff);
  sblive_write_reg (devc, VEDS, voiceR, 0x0);
  sblive_write_reg (devc, VTFT, voiceR, 0xffff);
  sblive_write_reg (devc, PTAB, voiceR, 0x0);

  /* now setup the voices and go! */
  setup_audio_voice (dev, voiceL, LEFT_CH);
  if (portc->channels == 2)
    setup_audio_voice (dev, voiceR, RIGHT_CH);

  portc->audio_active |= PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;

  if (portc->uses_spdif)
    {
      if (portc->format == AFMT_AC3)
	{
	  sblive_write_reg (devc, SCS0, 0, 0x2109206);
	}
      else
	{
	  sblive_write_reg (devc, SCS0, 0, 0x2108504);
	}
    }
  return 0;
}

static int
sblive_alloc_buffer (int dev, dmap_t * dmap, int direction)
{
  int err, i, n;
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;

  if (dmap->dmabuf != NULL)
    return 0;

  if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0)
    {
      cmn_err (CE_WARN, "Cannot allocate DMA buffer\n");
      return err;
    }

  if (dmap->buffsize > DMABUF_SIZE)
    {
      cmn_err (CE_NOTE, "DMA buffer was too large - truncated\n");
      dmap->buffsize = DMABUF_SIZE;
    }

  if (devc->feature_mask & SB_LIVE)
    if (dmap->dmabuf_phys & 0x80000000)
      {
	cmn_err (CE_CONT, "Got DMA buffer address beyond 2G limit.\n");
	oss_free_dmabuf (dev, dmap);
	dmap->dmabuf = NULL;

	return OSS_ENOSPC;
      }

  if (direction == PCM_ENABLE_OUTPUT)
    {
      dmap->driver_use_value = portc->memptr;
      n = portc->memptr / 4096;

/* 
 * Fill the page table
 */
      for (i = 0; i < dmap->buffsize / 4096; i++)
	{
	  FILL_PAGE_MAP_ENTRY (n + i, dmap->dmabuf_phys + i * 4096);
	}
    }

  return 0;
}

/*ARGSUSED*/
static int
sblive_free_buffer (int dev, dmap_t * dmap, int direction)
{
  if (dmap->dmabuf == NULL)
    return 0;
  oss_free_dmabuf (dev, dmap);

  dmap->dmabuf = NULL;
  return 0;
}

/*ARGSUSED*/
static int
sblive_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  sblive_devc *devc = audio_engines[dev]->devc;
  sblive_portc *portc = audio_engines[dev]->portc;
  int pos;

  if (!(portc->trigger_bits & direction))
    return 0;

  if (direction == OPEN_WRITE)
    {
      pos = sblive_read_reg (devc, QKBCA, portc->voice_chn) & 0x00ffffff;	/* Curr pos */
      pos <<= portc->out_sz;
      pos -= dmap->driver_use_value;
    }
  else
    {
      /* Compute current pos based on the wall clock register */
      pos = (INL (devc->osdev, devc->base + 0x10) >> 6) & 0xfffff;
      if (pos > portc->rec_starttime)
	pos = pos - portc->rec_starttime;
      else
	pos = 0xfffff - (portc->rec_starttime - pos);
      pos = (pos * (portc->speed / 25)) / (48000 / 25);
      pos *= 2;			/* 16 bit->bytes */
      pos *= portc->channels;
      pos = pos % dmap->bytes_in_use;
    }

  if (pos < 0)
    pos = 0;

  return pos;
}

static audiodrv_t sblive_audio_driver = {
  sblive_audio_open,
  sblive_audio_close,
  sblive_audio_output_block,
  sblive_audio_start_input,
  sblive_audio_ioctl,
  sblive_audio_prepare_for_input,
  sblive_audio_prepare_for_output,
  sblive_audio_reset,
  NULL,
  NULL,
  sblive_audio_reset_input,
  sblive_audio_reset_output,
  sblive_audio_trigger,
  sblive_audio_set_rate,
  sblive_audio_set_format,
  sblive_audio_set_channels,
  NULL,
  NULL,
  NULL,				/* sblive_check_input, */
  NULL,				/* sblive_check_output, */
  sblive_alloc_buffer,
  sblive_free_buffer,
  NULL,
  NULL,
  sblive_get_buffer_pointer,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  sblive_audio_ioctl		/* bind */
};

#define	DATAPORT   (devc->base)
#define	COMDPORT   (devc->base+1)
#define	STATPORT   (devc->base+1)

static __inline__ int
audigyuart_status (sblive_devc * devc)
{
  return sblive_read_reg (devc, MUASTAT, 0);
}

#define input_avail(devc) (!(audigyuart_status(devc)&INPUT_AVAIL))
#define output_ready(devc)	(!(audigyuart_status(devc)&OUTPUT_READY))
static void
audigyuart_cmd (sblive_devc * devc, unsigned char cmd)
{
  sblive_write_reg (devc, MUACMD, 0, cmd);
}

static __inline__ int
audigyuart_read (sblive_devc * devc)
{
  return sblive_read_reg (devc, MUADAT, 0);
}

static __inline__ void
audigyuart_write (sblive_devc * devc, unsigned char byte)
{
  sblive_write_reg (devc, MUADAT, 0, byte);
}

#define	OUTPUT_READY	0x40
#define	INPUT_AVAIL	0x80
#define	MPU_ACK		0xFE
#define	MPU_RESET	0xFF
#define	UART_MODE_ON	0x3F

static int reset_audigyuart (sblive_devc * devc);
static void enter_uart_mode (sblive_devc * devc);

typedef struct
{
  int keycode;
  int action;
  int local_action;
} ir_code_t;

static void
sblive_key_action (sblive_devc * devc, ir_code_t * code)
{
  int value, left, right, dev;

  dev = devc->mixer_dev;

  switch (code->local_action)
    {
    case 1:			/* Volume- */
      value = sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_READ, 0);
      left = value & 0xff;
      right = (value >> 8) & 0xff;
      left -= 5;
      if (left < 0)
	left = 0;
      right -= 5;
      if (right < 0)
	right = 0;
      value = left | (right << 8);
      sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_WRITE, value);
      mixer_devs[dev]->modify_counter++;
      return;
      break;

    case 2:			/* Volume+ */
      value = sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_READ, 0);
      left = value & 0xff;
      right = (value >> 8) & 0xff;
      left += 5;
      if (left > 100)
	left = 100;
      right += 5;
      if (right > 100)
	right = 100;
      value = left | (right << 8);
      sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_WRITE, value);
      mixer_devs[dev]->modify_counter++;
      return;
      break;
    }
}

static void
sblive_handle_ir (sblive_devc * devc, unsigned char c)
{
/*
 * Receive a MIDI SysEx message and check if it's an IR remote command
 */
#if 1

/*
 * Sysex code sent by the Live!DRIVE IR unit
 */
  static unsigned char remote_id[] =
    { 0xf0, 0x00, 0x20, 0x21, 0x60, 0x00, 0x02, 0x00, 0xf7 };

  static ir_code_t codes[] = {
    /* Creative RM-900B remote control unit */
    {0x09017e},			/* 0 */
    {0x0a512e},			/* 1 */
    {0x0a710e},			/* 2 */
    {0x090976},			/* 3 */
    {0x09512e},			/* 4 */
    {0x09215e},			/* 5 */
    {0x091e61},			/* 6 */
    {0x0a116e},			/* 7 */
    {0x0a413e},			/* 8 */
    {0x0a6e11},			/* 9 */
    {0x0a1e61},			/* Play/Pause */
    {0x0a215e},			/* Stop/Eject  */
    {0x0a3e41},			/* Slow */
    {0x0a7e01},			/* Prev */
    {0x095e21},			/* Next */
    {0x097e01},			/* Step */
    {0x097609},			/* Mute */
    {0x0a4639, 0, 1},		/* Vol- */
    {0x094639, 0, 2},		/* Vol+ */
    /* Speaker ??? */
    {0x09314e},			/* EAX */
    {0x09413e},			/* Options */
    {0x096e11},			/* Display */
    {0x09710e},			/* Return */
    {0x09116e},			/* Start */
    {0x093e41},			/* Cancel */
    {0x0a5e21},			/* Up */
    {0x0a611e},			/* << */
    {0x0a017e},			/* Select/OK */
    {0x0a2e51},			/* >> */
    {0x0a314e},			/* Down */

/* Creative RM-1000 remote control unit */
    {0x0a0679},			/* Power */
    {0x0a0e71},			/* CMSS */
    {0x0a4e31},			/* Rec */

/* Creative Inspire 5.1 Digital 5700 remote */
    {0x0a0778},			/* Power */
    {0x097708},			/* Mute */
    {0x0a7708},			/* Test */
    {0x0a4738},			/* Vol- */
    {0x094738},			/* Vol+ */
    {0x0a0f70},			/* Effect */
    {0x0a5728},			/* Analog */
    {0x0a2758},			/* Pro logic */
    {0x094f30},			/* Dynamic mode */
    {0x093748},			/* Digital/PCM audio */
    {0}
  };
#endif
  if (c == 0xf0)		/* Sysex start */
    {
      devc->sysex_buf[0] = c;
      devc->sysex_p = 1;
      return;
    }

  if (devc->sysex_p <= 0)
    return;

  if (devc->sysex_p >= 20)	/* Too long */
    {
      devc->sysex_p = 0;
      return;
    }

  if (c == 0xf7)		/* Sysex end */
    {
      int i, l, v;
      unsigned char *buf;

      devc->sysex_buf[devc->sysex_p] = c;
      devc->sysex_p++;
      l = devc->sysex_p;

      devc->sysex_p = 0;
      buf = devc->sysex_buf;

      if (l == 9)
	{
	  int ok = 1;

	  for (i = 0; i < sizeof (remote_id); i++)
	    if (buf[i] != remote_id[i])
	      ok = 0;

	  if (ok)
	    {
	      /* cmn_err (CE_CONT, "Live!DRIVE IR detected\n"); */
	      return;
	    }

	  return;
	}

      if (l != 13)		/* Wrong length */
	return;

      if (buf[0] != 0xf0 || buf[12] != 0xf7)	/* Not sysex */
	return;

      /* Verify that this is an IR receiver sysex */
      if (buf[1] != 0x00 || buf[2] != 0x20 || buf[3] != 0x21)
	return;
      if (buf[4] != 0x60 || buf[5] != 0x00 || buf[6] != 0x01)
	return;
#if 0
      if (buf[7] != 0x09 && buf[7] != 0x0a)	/* Remote ID */
	return;
#endif
      if (buf[8] != 0x41 || buf[9] != 0x44)
	return;

      v = (buf[7] << 16) | (buf[10] << 8) | buf[11];

      for (i = 0; codes[i].keycode != 0; i++)
	if (codes[i].keycode == v)
	  {
	    sblive_key_action (devc, &codes[i]);
	    return;
	  }

      return;
    }

  /* Ordinary byte */
  devc->sysex_buf[devc->sysex_p] = c;
  devc->sysex_p++;
}

static void
sblive_ir_callback (int dev, unsigned char c)
{
  sblive_devc *devc;
  oss_device_t *osdev = midi_devs[dev]->osdev;
  devc = osdev->devc;

  if (devc->midi_dev != dev)
    return;

  sblive_handle_ir (devc, c);
}

static void
audigyuart_input_loop (sblive_devc * devc)
{
  int t = 0;

  while (input_avail (devc) && t++ < 1000)
    {
      unsigned char c = audigyuart_read (devc);

      sblive_handle_ir (devc, c);

      if (c == MPU_ACK)
	devc->input_byte = c;
      else if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
	devc->midi_input_intr (devc->midi_dev, c);
    }
}

static void
audigyuartintr (sblive_devc * devc)
{
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  audigyuart_input_loop (devc);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
audigyuart_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
		 oss_midi_inputbuf_t inputbuf,
		 oss_midi_outputintr_t outputintr)
{
  sblive_devc *devc = (sblive_devc *) midi_devs[dev]->devc;

  if (devc->midi_opened)
    {
      return OSS_EBUSY;
    }

  while (input_avail (devc))
    audigyuart_read (devc);

  devc->midi_input_intr = inputbyte;
  devc->midi_opened = mode;
  enter_uart_mode (devc);
  devc->midi_disabled = 0;

  return 0;
}

/*ARGSUSED*/
static void
audigyuart_close (int dev, int mode)
{
  sblive_devc *devc = (sblive_devc *) midi_devs[dev]->devc;

  reset_audigyuart (devc);
  oss_udelay (10);
  enter_uart_mode (devc);
  reset_audigyuart (devc);
  devc->midi_opened = 0;
}

static int
audigyuart_out (int dev, unsigned char midi_byte)
{
  sblive_devc *devc = (sblive_devc *) midi_devs[dev]->devc;

  if (devc->midi_disabled)
    return 1;

  if (!output_ready (devc))
    {
      return 0;
    }

  audigyuart_write (devc, midi_byte);
  return 1;
}

/*ARGSUSED*/
static int
audigyuart_ioctl (int dev, unsigned cmd, ioctl_arg arg)
{
  return OSS_EINVAL;
}

static midi_driver_t audigy_midi_driver = {
  audigyuart_open,
  audigyuart_close,
  audigyuart_ioctl,
  audigyuart_out
};

static void
enter_uart_mode (sblive_devc * devc)
{
  int ok, timeout;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);

  devc->input_byte = 0;
  audigyuart_cmd (devc, UART_MODE_ON);

  ok = 0;
  for (timeout = 50000; timeout > 0 && !ok; timeout--)
    if (devc->input_byte == MPU_ACK)
      ok = 1;
    else if (input_avail (devc))
      if (audigyuart_read (devc) == MPU_ACK)
	ok = 1;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

void
attach_audigyuart (sblive_devc * devc)
{
  enter_uart_mode (devc);

  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "AUDIGY", "Audigy UART", &audigy_midi_driver, sizeof (midi_driver_t),
					0, devc, devc->osdev);
  devc->midi_opened = 0;
}

static int
reset_audigyuart (sblive_devc * devc)
{
  int ok, timeout, n;
  oss_native_word flags;

  /*
   * Send the RESET command. Try again if no success at the first time.
   */

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

  for (n = 0; n < 2 && !ok; n++)
    {
      for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);

      devc->input_byte = 0;
      audigyuart_cmd (devc, MPU_RESET);

      /*
       * Wait at least 25 msec. This method is not accurate so let's make the
       * loop bit longer. Cannot sleep since this is called during boot.
       */

      for (timeout = 50000; timeout > 0 && !ok; timeout--)
	if (devc->input_byte == MPU_ACK)	/* Interrupt */
	  ok = 1;
	else if (input_avail (devc))
	  if (audigyuart_read (devc) == MPU_ACK)
	    ok = 1;

    }



  if (ok)
    audigyuart_input_loop (devc);	/*
					 * Flush input before enabling interrupts
					 */

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return ok;
}

int
probe_audigyuart (sblive_devc * devc)
{
  int ok = 0;

  DDB (cmn_err (CE_CONT, "Entered probe_audigyuart\n"));

  devc->midi_input_intr = NULL;
  devc->midi_opened = 0;
  devc->input_byte = 0;

  ok = reset_audigyuart (devc);

  if (ok)
    {
      DDB (cmn_err (CE_CONT, "Reset UART401 OK\n"));
    }
  else
    {
      DDB (cmn_err
	   (CE_CONT, "Reset UART401 failed (no hardware present?).\n"));
      DDB (cmn_err
	   (CE_CONT, "mpu401 status %02x\n", audigyuart_status (devc)));
    }

  DDB (cmn_err (CE_CONT, "audigyuart detected OK\n"));
  return ok;
}

void
unload_audigyuart (sblive_devc * devc)
{
  reset_audigyuart (devc);
}

static void
attach_mpu (sblive_devc * devc)
{
  char tmp[128];
  int ndevs = num_mididevs;
  oss_native_word flags;

  sprintf (tmp, "%s external MIDI", devc->card_name);

  if (devc->feature_mask & SB_AUDIGY)
    {
      if (!probe_audigyuart (devc))
	{
	  cmn_err (CE_NOTE, "MIDI UART was not detected\n");
	  return;
	}
      DDB (cmn_err (CE_CONT, "SB Audigy: MIDI UART detected - Good\n"));
      devc->mpu_attached = 1;
      attach_audigyuart (devc);
    }
  else
    {
      MUTEX_ENTER (devc->mutex, flags);
      if (uart401_init (&devc->uart401devc, devc->osdev, devc->base + 0x18,
			tmp) >= 0)
	devc->mpu_attached = 1;
      MUTEX_EXIT (devc->mutex, flags);

      if (ndevs != num_mididevs)
	{
	  devc->midi_dev = ndevs;
	  midi_devs[ndevs]->input_callback = sblive_ir_callback;
	}
    }
}

static void
load_dsp (sblive_devc * devc, unsigned char *buf, int len)
{
  emu10k1_file *code;
  int pc, i;

  if (len != sizeof (*code))
    {
      cmn_err (CE_NOTE, "DSP file size mismatch\n");
      return;
    }

  code = (emu10k1_file *) buf;

  for (pc = 0; pc < 1024; pc++)
    {
      write_efx (devc, UC0 + pc, code->code[pc]);
    }

  if (code->parms.ngpr < MAX_GPR_PARMS)
    for (i = 0; i < code->parms.ngpr; i++)
      {
	code->parms.gpr[i].name[GPR_NAME_SIZE - 1] = 0;	/* Overflow protection */
	if (strlen (code->parms.gpr[i].name) >= 32)	/* Name may be bad */
	  {
	    return;
	  }

/* cmn_err(CE_CONT, "Gpr %d = %s (vol %x) type=%x\n", gpr->gpr[i].num, gpr->gpr[i].name, gpr->gpr[i].def, gpr->gpr[i].type); */
	if (code->parms.gpr[i].num < MAX_GPR)
	  if (code->parms.gpr[i].type != MIXT_GROUP)
	    {
	      if (is_special_gpr (code->parms.gpr[i].num))
		sblive_set_gpr (devc->mixer_dev, code->parms.gpr[i].num,
				SNDCTL_MIX_WRITE,
				devc->gpr_values[code->parms.gpr[i].num]);
	      else
		sblive_set_gpr (devc->mixer_dev, code->parms.gpr[i].num,
				SNDCTL_MIX_WRITE, code->parms.gpr[i].def);
	    }
      }


  if (devc->gpr == NULL)
    {
      devc->gpr = PMALLOC (devc->osdev, sizeof (gpr_info));
      if (devc->gpr == NULL)
	{
	  cmn_err (CE_WARN, "Out of memory (gpr)\n");
	  return;
	}
      memset (devc->gpr, 0, sizeof (gpr_info));
    }
  memcpy (devc->gpr, &code->parms, sizeof (gpr_info));
  create_efx_mixer (devc->mixer_dev);

  if (code->consts.nconst >= MAX_CONST_PARMS)
    return;

  for (i = 0; i < code->consts.nconst; i++)
    {
      if (code->consts.consts[i].gpr >= MAX_GPR)
	{
	  return;
	}

      sblive_write_reg (devc, code->consts.consts[i].gpr + GPR0, 0,
			code->consts.consts[i].value);
    }
}

#define LIVE_NOP() \
        write_efx(devc, UC0+(pc*2), 0x10040); \
        write_efx(devc, UC0+(pc*2+1), 0x610040);pc++
#define LIVE_ACC3(r, a, x, y) /* z=w+x+y */ \
    write_efx(devc, UC0+(pc*2), (x << 10) | y); \
    write_efx(devc, UC0+(pc*2+1), (6 << 20) | (r << 10) | a);pc++

#define AUDIGY_ACC3(r, a, x, y) /* z=w+x+y */ \
    write_efx(devc, UC0+(pc*2), (x << 12) | y); \
    write_efx(devc, UC0+(pc*2+1), (6 << 24) | (r << 12) | a);pc++
#define AUDIGY_NOP() AUDIGY_ACC3(0xc0, 0xc0, 0xc0, 0xc0)

static int
init_effects (sblive_devc * devc)
{
  int i;
  unsigned short pc;

  if (devc->feature_mask & SB_AUDIGY)
    {
      pc = 0;
      for (i = 0; i < 512; i++)
	{
	  AUDIGY_NOP ();
	}

      for (i = 0; i < 256; i++)
	write_efx (devc, GPR0 + i, 0);
      sblive_write_reg (devc, AUDIGY_DBG, 0, 0);
      load_dsp (devc, emu10k2_dsp, sizeof (emu10k2_dsp));
    }
  else
    {
      pc = 0;
      for (i = 0; i < 512; i++)
	{
	  LIVE_NOP ();
	}

      for (i = 0; i < 256; i++)
	write_efx (devc, GPR0 + i, 0);
      sblive_write_reg (devc, DBG, 0, 0);
      load_dsp (devc, emu10k1_dsp, sizeof (emu10k1_dsp));
    }

  return 1;
}

static void
init_emu10k1 (sblive_devc * devc)
{
  unsigned int tmp, i;
  extern int sblive_memlimit;
#ifndef NO_EMU10K1_SYNTH
  extern int sblive_synth_enable;
#endif
  int xmem_mode = 0;
  unsigned int reg, val;
  extern int sblive_digital_din;
  extern int audigy_digital_din;
  oss_native_word phaddr;
  unsigned int memlimit = MEMLIMIT_31BITS;

  OUTL (devc->osdev, 0x00000000, devc->base + 0x0c);	/* Intr disable */
  OUTL (devc->osdev,
	HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE,
	devc->base + 0x14);

  sblive_write_reg (devc, MBS, 0, 0x0);
  sblive_write_reg (devc, MBA, 0, 0x0);
  sblive_write_reg (devc, FXBS, 0, 0x0);
  sblive_write_reg (devc, FXBA, 0, 0x0);
  sblive_write_reg (devc, ADCBS, 0, 0x0);
  sblive_write_reg (devc, ADCBA, 0, 0x0);

  sblive_write_reg (devc, CLIEL, 0, 0x0);
  sblive_write_reg (devc, CLIEH, 0, 0x0);
  sblive_write_reg (devc, SOLL, 0, 0xffffffff);
  sblive_write_reg (devc, SOLH, 0, 0xffffffff);


  if (devc->feature_mask & SB_AUDIGY)
    {
      memlimit=MEMLIMIT_32BITS;
      sblive_write_reg (devc, 0x5e, 0, 0xf00);	/* ?? */
      sblive_write_reg (devc, 0x5f, 0, 0x3);	/* ?? */
    }

#ifndef NO_EMU10K1_SYNTH
  if (!sblive_synth_enable)
    sblive_memlimit = 0;
#endif

  if (sblive_memlimit < 4096)	/* Given in megabytes */
    sblive_memlimit *= (1024 * 1024);

  devc->max_mem = sblive_memlimit;
  if (devc->max_mem < 1024 * 1024)
    devc->max_mem = 1024 * 1024;

  devc->max_mem += AUDIO_MEMSIZE;

  /* SB Live/Audigy supports at most 32M of memory) */
  if (devc->max_mem > 32 * 1024 * 1024)
    devc->max_mem = 32 * 1024 * 1024;

  devc->max_pages = devc->max_mem / 4096;
  if (devc->max_pages < 1024)
    devc->max_pages = 1024;
  devc->page_map =
    (int *) CONTIG_MALLOC (devc->osdev, devc->max_pages * 4, memlimit,
			   &phaddr, devc->page_map_dma_handle);
  devc->vpage_map =
    KERNEL_MALLOC (devc->max_pages * sizeof (unsigned char *));
  if (devc->page_map == NULL || devc->vpage_map == NULL)
    {
      cmn_err (CE_WARN, "Can't allocate the PTBA table\n");
      return;
    }
  memset (devc->vpage_map, 0, devc->max_pages * 4);

  tmp = phaddr;
  if (devc->feature_mask & SB_LIVE)
    {
      if (tmp & 0x80000000)
	{
	  cmn_err (CE_CONT,
		   "SB Live Error: Page table is beyond the 2G limit\n");
	}
    }
  else
    {
      if (tmp & 0x80000000)
	{
	  DDB (cmn_err (CE_CONT, "Audigy: Using 4G PCI addressing mode\n"));
	  xmem_mode = 1;
	  devc->emu_page_shift = 0;
	  if (devc->max_mem > 16 * 1024 * 1034)
	    {
	      devc->max_mem = 16 * 1024 * 1024;

	      DDB (cmn_err
		   (CE_NOTE,
		    "Max memory dropped to 16M due to need for extended PCI address mode.\n"));
	    }
	}
    }

  devc->synth_memlimit = devc->max_mem - AUDIO_MEMSIZE;
  devc->synth_membase = SYNTH_MEMBASE;
  devc->synth_memtop = devc->synth_membase;
  devc->synth_memptr = devc->synth_membase;

  devc->silent_page =
    (int *) CONTIG_MALLOC (devc->osdev, 4096, memlimit, &phaddr, devc->silent_page_dma_handle);
  if (devc->silent_page == NULL)
    {
      cmn_err (CE_WARN, "Can't allocate a silent page\n");
      return;
    }

  devc->silent_page_phys = phaddr;
  if (devc->feature_mask & SB_LIVE)
    if (devc->silent_page_phys & 0x80000000)
      {
	cmn_err (CE_CONT,
		 "SB Live warning: Silent page is beyond the 2G limit\n");
      }

  devc->audio_memptr = 4096;	/* Skip the silence page */
  memset (devc->silent_page, 0, 4096);

  for (i = 0; i < devc->max_pages; i++)
    {
      FILL_PAGE_MAP_ENTRY (i, devc->silent_page_phys);
      devc->vpage_map[i] = NULL;
    }

  for (i = 0; i < 64; i++)
    sblive_init_voice (devc, i);

  if (devc->feature_mask & SB_AUDIGY)
    {
      sblive_write_reg (devc, SCS0, 0, 0x2108504);
      sblive_write_reg (devc, SCS1, 0, 0x2108504);
      sblive_write_reg (devc, SCS2, 0, 0x2108504);
    }
  else
    {
      sblive_write_reg (devc, SCS0, 0, 0x2109204);
      sblive_write_reg (devc, SCS1, 0, 0x2109204);
      sblive_write_reg (devc, SCS2, 0, 0x2109204);
    }

  sblive_write_reg (devc, PTBA, 0, tmp);
  tmp = sblive_read_reg (devc, PTBA, 0);

  sblive_write_reg (devc, TCBA, 0, 0x0);
  sblive_write_reg (devc, TCBS, 0, 0x4);

  OUTL (devc->osdev, IE_RXA | IE_AB | IE_IT, devc->base + IE);	/* Intr enable */

/*
 * SB Live 5.1 support. Turn on S/PDIF output
 */
  if (devc->subvendor == 0x80611102)	/* Live 5.1 */
    {
      tmp = INL (devc->osdev, devc->base + 0x14);
      tmp |= 0x00001000;	/* Turn GPO0 pin on to enable S/PDIF outputs */
      OUTL (devc->osdev, tmp, devc->base + 0x14);
    }

  if (devc->subvendor == 0x80661102)
    {
      sblive_write_reg (devc, AC97SLOT, 0,
			AC97SLOT_CENTER | AC97SLOT_LFE | AC97SLOT_REAR_LEFT |
			AC97SLOT_REAR_RIGHT);
    }

  if (devc->feature_mask & SB_AUDIGY2)
    {
      /* Enable analog outputs on Audigy2 */
      int tmp;

      /* Setup SRCMulti_I2S SamplingRate */
      tmp = sblive_read_reg (devc, EHC, 0);
      tmp &= 0xfffff1ff;
      tmp |= (0x2 << 9);
      sblive_write_reg (devc, EHC, 0, tmp);
      /* sblive_write_reg (devc, SOC, 0, 0x00000000); */

      /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
      OUTL (devc->osdev, 0x600000, devc->base + 0x20);
      OUTL (devc->osdev, 0x14, devc->base + 0x24);

      /* Setup SRCMulti Input Audio Enable */
      /* Setup SRCMulti Input Audio Enable */
      if (devc->feature_mask & SB_AUDIGY2VAL)
	OUTL (devc->osdev, 0x7B0000, devc->base + 0x20);
      else
	OUTL (devc->osdev, 0x6E0000, devc->base + 0x20);

      OUTL (devc->osdev, 0xFF00FF00, devc->base + 0x24);

      /* Setup I2S ASRC Enable  (HC register) */
      tmp = INL (devc->osdev, devc->base + 0x14);
      tmp |= 0x00000070;
      OUTL (devc->osdev, tmp, devc->base + 0x14);

      /*
       * Unmute Analog now.  Set GPO6 to 1 for Apollo.
       * This has to be done after init ALice3 I2SOut beyond 48KHz.
       * So, sequence is important
       */
      tmp = INL (devc->osdev, devc->base + 0x18);
      tmp |= 0x0040;
      if (devc->feature_mask & SB_AUDIGY2VAL)
	tmp |= 0x0060;

      OUTL (devc->osdev, tmp, devc->base + 0x18);
    }

  sblive_write_reg (devc, SOLL, 0, 0xffffffff);
  sblive_write_reg (devc, SOLH, 0, 0xffffffff);

  if (devc->feature_mask & SB_AUDIGY)
    {
      unsigned int mode = 0;

      if (devc->feature_mask & SB_AUDIGY2)
	mode |= HCFG_AC3ENABLE_GPSPDIF | HCFG_AC3ENABLE_CDSPDIF;
      if (xmem_mode)
	{
	  OUTL (devc->osdev,
		HCFG_AUDIOENABLE | HCFG_AUTOMUTE | HCFG_JOYENABLE |
		A_HCFG_VMUTE | A_HCFG_AUTOMUTE | A_HCFG_XM | mode,
		devc->base + 0x14);
	}
      else
	OUTL (devc->osdev,
	      HCFG_AUDIOENABLE | HCFG_AUTOMUTE | HCFG_JOYENABLE | A_HCFG_VMUTE
	      | A_HCFG_AUTOMUTE | mode, devc->base + 0x14);

      OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x18) | 0x0004, devc->base + 0x18);	/* GPIO (S/PDIF enable) */


      /* enable IR port */
      tmp = INL (devc->osdev, devc->base + 0x18);
      OUTL (devc->osdev, tmp | A_IOCFG_GPOUT2, devc->base + 0x18);
      oss_udelay (500);
      OUTL (devc->osdev, tmp | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2,
	    devc->base + 0x18);
      oss_udelay (100);
      OUTL (devc->osdev, tmp, devc->base + 0x18);
    }
  else
    OUTL (devc->osdev,
	  HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE_MASK | HCFG_AUTOMUTE |
	  HCFG_JOYENABLE, devc->base + 0x14);


  /* enable IR port */
  tmp = INL (devc->osdev, devc->base + 0x14);
  OUTL (devc->osdev, tmp | HCFG_GPOUT2, devc->base + 0x14);
  oss_udelay (500);
  OUTL (devc->osdev, tmp | HCFG_GPOUT1 | HCFG_GPOUT2, devc->base + 0x14);
  oss_udelay (100);
  OUTL (devc->osdev, tmp, devc->base + 0x14);

  /* Switch the shared SPDIF/OUT3 to DIGITAL or ANALOG mode */
  /* depending on whether the port is SPDIF or analog */

  if ((devc->feature_mask == SB_AUDIGY) ||
      ((devc->feature_mask & SB_AUDIGY2) && (audigy_digital_din == 0)))
    {
      reg = INL (devc->osdev, devc->base + 0x18) & ~A_IOCFG_GPOUT0;
      val = (audigy_digital_din) ? 0x4 : 0;
      reg |= val;
      OUTL (devc->osdev, reg, devc->base + 0x18);
    }
  if (devc->feature_mask & SB_LIVE)	/* SBLIVE */
    {
      reg = INL (devc->osdev, devc->base + 0x14) & ~HCFG_GPOUT0;
      val = (sblive_digital_din) ? HCFG_GPOUT0 : 0;
      reg |= val;
      OUTL (devc->osdev, reg, devc->base + 0x14);
    }

}

void
sblive_init_voice (sblive_devc * devc, int voice)
{
  sblive_set_loop_stop (devc, voice, 1);

  sblive_write_reg (devc, VEDS, voice, 0x0);
  sblive_write_reg (devc, IP, voice, 0x0);
  sblive_write_reg (devc, VTFT, voice, 0xffff);
  sblive_write_reg (devc, CVCF, voice, 0xffff);
  sblive_write_reg (devc, PTAB, voice, 0x0);
  sblive_write_reg (devc, CPF, voice, 0x0);
  sblive_write_reg (devc, CCR, voice, 0x0);
  sblive_write_reg (devc, SCSA, voice, 0x0);
  sblive_write_reg (devc, SDL, voice, 0x10);
  sblive_write_reg (devc, QKBCA, voice, 0x0);
  sblive_write_reg (devc, Z1, voice, 0x0);
  sblive_write_reg (devc, Z2, voice, 0x0);

  if (devc->feature_mask & SB_AUDIGY)
    sblive_write_reg (devc, SRDA, voice, 0x03020100);
  sblive_write_reg (devc, FXRT, voice, 0x32100000);

  sblive_write_reg (devc, MEHA, voice, 0x0);
  sblive_write_reg (devc, MEDS, voice, 0x0);
  sblive_write_reg (devc, IFA, voice, 0xffff);
  sblive_write_reg (devc, PEFE, voice, 0x0);
  sblive_write_reg (devc, VFM, voice, 0x0);
  sblive_write_reg (devc, TMFQ, voice, 24);
  sblive_write_reg (devc, VVFQ, voice, 24);
  sblive_write_reg (devc, TMPE, voice, 0x0);
  sblive_write_reg (devc, VLV, voice, 0x0);
  sblive_write_reg (devc, MLV, voice, 0x0);
  sblive_write_reg (devc, VEHA, voice, 0x0);
  sblive_write_reg (devc, VEV, voice, 0x0);
  sblive_write_reg (devc, MEV, voice, 0x0);

  if (devc->feature_mask & SB_AUDIGY)
    {
      sblive_write_reg (devc, CSBA, voice, 0x0);
      sblive_write_reg (devc, CSDC, voice, 0x0);
      sblive_write_reg (devc, CSFE, voice, 0x0);
      sblive_write_reg (devc, CSHG, voice, 0x0);
      sblive_write_reg (devc, SRHE, voice, 0x3f3f3f3f);
    }
}

#ifndef NO_EMU10K1_SYNTH
extern void sblive_install_synth (sblive_devc * devc);
extern void sblive_remove_synth (sblive_devc * devc);
#endif

static const unsigned char peak_cnv[256] = {
  0, 18, 29, 36, 42, 47, 51, 54, 57, 60, 62, 65, 67, 69, 71, 72,
  74, 75, 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 89, 90,
  91, 92, 93, 93, 94, 95, 95, 96, 97, 97, 98, 99, 99, 100, 100, 101,
  101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108, 108,
  108,
  109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 113, 113, 113, 114, 114,
  114,
  115, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 118, 119,
  119,
  119, 119, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123,
  123,
  123, 123, 124, 124, 124, 124, 125, 125, 125, 125, 125, 126, 126, 126, 126,
  126,
  127, 127, 127, 127, 127, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129,
  130,
  130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 131, 132, 132, 132, 132,
  132,
  132, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 135,
  135,
  135, 135, 135, 135, 135, 136, 136, 136, 136, 136, 136, 136, 137, 137, 137,
  137,
  137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139,
  139,
  139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141,
  141,
  141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 142, 143, 143,
  143,
  143, 143, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 144, 144, 144,
  144,
};

static void
set_equalizer (sblive_devc * devc, int ctrl, int band, int value)
{
  const unsigned int *row;
  int i;

  switch (band)
    {
    case 0:
      row = (unsigned int *) &eq_band1_data[value][0];
      break;
    case 1:
      row = (unsigned int *) &eq_band2_data[value][0];
      break;
    case 2:
      row = (unsigned int *) &eq_band3_data[value][0];
      break;
    case 3:
      row = (unsigned int *) &eq_band4_data[value][0];
      break;

    default:
      cmn_err (CE_CONT, "%s: bad equalizer band %d\n", devc->card_name, band);
      return;
    }

  for (i = 0; i < 5; i++)
    {
      sblive_write_reg (devc, ctrl + GPR0 + i, 0, row[i]);
    }
}

static const int db2lin_101[101] = { 0x00000000,
  0x0024B53A, 0x002750CA, 0x002A1BC6, 0x002D198D, 0x00304DBA, 0x0033BC2A,
  0x00376901, 0x003B58AF, 0x003F8FF1, 0x004413DF, 0x0048E9EA, 0x004E17E9,
  0x0053A419, 0x0059952C, 0x005FF24E, 0x0066C32A, 0x006E0FFB, 0x0075E18D,
  0x007E414F, 0x0087395B, 0x0090D482, 0x009B1E5B, 0x00A6234F, 0x00B1F0A7,
  0x00BE94A1, 0x00CC1E7C, 0x00DA9E8D, 0x00EA2650, 0x00FAC881, 0x010C9931,
  0x011FADDC, 0x01341D87, 0x014A00D8, 0x01617235, 0x017A8DE6, 0x01957233,
  0x01B23F8D, 0x01D118B1, 0x01F222D4, 0x021585D1, 0x023B6C57, 0x0264041D,
  0x028F7E19, 0x02BE0EBD, 0x02EFEE33, 0x032558A2, 0x035E8E7A, 0x039BD4BC,
  0x03DD7551, 0x0423BF61, 0x046F07B5, 0x04BFA91B, 0x051604D5, 0x0572830D,
  0x05D59354, 0x063FAD27, 0x06B15080, 0x072B0673, 0x07AD61CD, 0x0838FFCA,
  0x08CE88D3, 0x096EB147, 0x0A1A3A53, 0x0AD1F2E0, 0x0B96B889, 0x0C6978A5,
  0x0D4B316A, 0x0E3CF31B, 0x0F3FE155, 0x10553469, 0x117E3AD9, 0x12BC5AEA,
  0x14111457, 0x157E0219, 0x1704DC5E, 0x18A77A97, 0x1A67D5B6, 0x1C480A87,
  0x1E4A5C45, 0x2071374D, 0x22BF3412, 0x25371A37, 0x27DBE3EF, 0x2AB0C18F,
  0x2DB91D6F, 0x30F89FFD, 0x34733433, 0x382D0C46, 0x3C2AA6BD, 0x4070D3D9,
  0x4504BB66, 0x49EBE2F1, 0x4F2C346F, 0x54CC0565, 0x5AD21E86, 0x6145C3E7,
  0x682EBDBD, 0x6F9561C4, 0x77829D4D,
  0x7fffffff
};

static __inline__ int
convert_fixpoint (int val)
{
  if (val < 0)
    val = 0;
  if (val > 100)
    val = 100;
  return db2lin_101[val];
}

static int
sblive_set_gpr (int dev, int ctrl, unsigned int cmd, int value)
{
  sblive_devc *devc = mixer_devs[dev]->hw_devc;
  int typ, i;

  if (devc == NULL)
    return 0;

  if (devc->gpr == NULL)
    {
      int left, right;

      if (ctrl >= NEXT_FREE_GPR)
	return 0;

      if (cmd != SNDCTL_MIX_WRITE)
	return 0;

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

      if (left < 0)
	left = 0;
      if (left > 100)
	left = 100;
      if (right < 0)
	right = 0;
      if (right > 100)
	right = 100;
      value = left | (right << 8);
      devc->gpr_values[ctrl] = value;

      left = convert_fixpoint (left);
      sblive_write_reg (devc, ctrl + GPR0, 0, left);
      right = convert_fixpoint (right);
      sblive_write_reg (devc, ctrl + GPR0 + 1, 0, right);
      return value;
    }

  if (ctrl < 0 || ctrl >= MAX_GPR)
    return OSS_EIO;

  typ = MIXT_SLIDER;
  for (i = 0; i < devc->gpr->ngpr; i++)
    if (devc->gpr->gpr[i].num == ctrl && devc->gpr->gpr[i].type != MIXT_GROUP)
      typ = devc->gpr->gpr[i].type;

  if (typ == MIXT_GROUP)
    {
      return OSS_EIO;
    }

  if (cmd == SNDCTL_MIX_READ)
    {
      if (typ == MIXT_STEREOPEAK || typ == MIXT_STEREOVU)
	{
	  int v, l, r;

	  /* Get the sample values and scale them to 0-144 dB range */
	  v = sblive_read_reg (devc, ctrl + GPR0, 0);
	  l = v >> 23;

	  v = sblive_read_reg (devc, ctrl + GPR0 + 1, 0);
	  r = v >> 23;

	  if (l < 0)
	    l = -l;
	  if (r < 0)
	    r = -r;
	  l = peak_cnv[l];
	  r = peak_cnv[r];

	  /* Reset values back to 0 */
	  sblive_write_reg (devc, ctrl + GPR0, 0, 0);
	  sblive_write_reg (devc, ctrl + GPR0 + 1, 0, 0);

	  return l | (r << 8);
	}
      return devc->gpr_values[ctrl];
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (typ)
	{
	case MIXT_STEREOSLIDER:
	  {
	    int left, right;

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

	    if (left < 0)
	      left = 0;
	    if (left > 100)
	      left = 100;
	    if (right < 0)
	      right = 0;
	    if (right > 100)
	      right = 100;
	    value = left | (right << 8);
	    devc->gpr_values[ctrl] = value;

	    left = convert_fixpoint (left);
	    sblive_write_reg (devc, ctrl + GPR0, 0, left);
	    right = convert_fixpoint (right);
	    sblive_write_reg (devc, ctrl + GPR0 + 1, 0, right);
	  }
	  break;

	case MIXT_ONOFF:
	  {
	    value = !!value;
	    devc->gpr_values[ctrl] = value;

	    sblive_write_reg (devc, ctrl + GPR0, 0, value);
	    sblive_write_reg (devc, ctrl + GPR0 + 1, 0, !value);
	  }
	  break;

	case EMU_MIXT_EQ1:
	case EMU_MIXT_EQ2:
	case EMU_MIXT_EQ3:
	case EMU_MIXT_EQ4:
	  {
	    int band;

	    band = typ & 3;
	    value = value & 0xff;
	    set_equalizer (devc, ctrl, band, value);
	    devc->gpr_values[ctrl] = value;
	  }
	  break;

	default:
	  {
	    int tmp;

	    value = value & 0xff;
	    if (value > 100)
	      value = 100;

	    devc->gpr_values[ctrl] = value;

	    tmp = convert_fixpoint (value);
	    sblive_write_reg (devc, ctrl + GPR0, 0, tmp);
	  }
	}

      return value;
    }

  return OSS_EINVAL;
}

static int
sblive_set_vol (int dev, int ctrl, unsigned int cmd, int value)
{
  sblive_devc *devc = mixer_devs[dev]->hw_devc;
  sblive_portc *portc;

  if (ctrl < 0 || ctrl >= devc->n_audiodevs)
    return OSS_EINVAL;

  portc = &devc->portc[ctrl];

  if (portc->input_type == ITYPE_SPDIF)
    {
      mixer_devs[dev]->modify_counter++;
      return 100 | (100 << 8);
    }

  if (cmd == SNDCTL_MIX_READ)
    {
#ifdef TEST_3D
      return (devc->portc[ctrl].playvol & 0x00ff) |
	((devc->portc[ctrl].playangle & 0xffff) << 16) |
	((devc->portc[ctrl].playdist & 0xff) << 8);
#else
      return devc->portc[ctrl].playvol & 0xff;
#endif
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
#ifdef TEST_3D
      int angle, dist;
      angle = (value >> 16) & 0xffff;	/* Rotation angle */
      dist = (value >> 8) & 0xff;	/* Distance */
      value &= 0x00ff;		/* Volume */

      if (value < 0)
	value = 0;
      if (value > 100)
	value = 100;

      switch (portc->speaker_mode)
	{
	case SMODE_FRONT:
	  angle = 0;
	  dist = 50;
	  break;

	case SMODE_SURR:
	  angle = 180;
	  dist = 50;
	  break;

	case SMODE_FRONTREAR:
	  angle = 0;
	  dist = 50;
	  break;

	case SMODE_3D:
	  break;
	}
      devc->portc[ctrl].playvol = value;
      devc->portc[ctrl].playdist = dist;
      devc->portc[ctrl].playangle = angle;

      update_output_device (devc, &devc->portc[ctrl]);
      return (value & 0x00ff) | (angle << 16) | ((dist & 0xff) << 8);
#else
      value &= 0xff;		/* Only left channel */

      if (value < 0)
	value = 0;
      if (value > 100)
	value = 100;
      devc->portc[ctrl].playvol = value;

      update_output_device (devc, &devc->portc[ctrl]);
      return value;

#endif
    }

  return OSS_EINVAL;
}

/*ARGSUSED*/
static int
sblive_get_peak (int dev, int ctrl, unsigned int cmd, int value)
{
  sblive_devc *devc = mixer_devs[dev]->hw_devc;

  if (ctrl < 0 || ctrl >= devc->n_audiodevs)
    return OSS_EINVAL;

  if (cmd == SNDCTL_MIX_READ)
    {
      int l, r, vol;

      l = devc->portc[ctrl].vu_left & 0xff;
      r = devc->portc[ctrl].vu_right & 0xff;
#if 1
      vol = devc->portc[ctrl].playvol;
      /* if (vol<1) vol=5; */
      l = (l * vol + 50) / 100;
      r = (r * vol + 50) / 100;
#endif
      devc->portc[ctrl].vu_left = 0;
      devc->portc[ctrl].vu_right = 0;

      return peak_cnv[l] | (peak_cnv[r] << 8);
    }

  return OSS_EINVAL;
}

static int
sblive_set_parm (int dev, int ctrl, unsigned int cmd, int value)
{
  sblive_devc *devc = mixer_devs[dev]->hw_devc;

  if (cmd == SNDCTL_MIX_READ)
    {
      switch (ctrl)
	{
	case 1:
	  return devc->autoreset;
	case 2:
	  return devc->speaker_mode;
	}
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (ctrl)
	{
	case 1:
	  return devc->autoreset = !!(value);
	case 2:
	  if (devc->speaker_mode != value)
	    {
	      int i;
	      for (i = 0; i < devc->n_audiodevs; i++)
		devc->portc[i].resetvol = 1;
	    }
	  return devc->speaker_mode = value;
	  break;

	}
    }

  return OSS_EINVAL;
}

static int
create_soft_mixer (int dev)
{
  sblive_devc *devc = mixer_devs[dev]->hw_devc;
  int group = 0, err, i, n;
  char tmp[100];

  if ((err = mixer_ext_create_control (dev, 0,
				       1,
				       sblive_set_parm,
				       MIXT_ONOFF,
				       "SBLIVE_AUTORESET",
				       2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

#ifdef TEST_3D
  n = 5;
#else
  n = 4;
#endif
  if ((err = mixer_ext_create_control (dev, 0,
				       2,
				       sblive_set_parm,
				       MIXT_ENUM,
				       "SBLIVE_SPKMODE",
				       n,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  for (i = 0; i < devc->n_audiodevs; i++)
    {
      if (devc->n_audiodevs > devc->min_audiodevs)
	{
	  /*
	   * Use the traditional dspN naming system for sliders.
	   */
	  if ((i % 8) == 0)
	    if ((group = mixer_ext_create_group (dev, 0, "/dev")) < 0)
	      return group;

	  sprintf (tmp, "@pcm%d", devc->portc[i].audiodev);
	}
      else
	{
	  /*
	   * Use front/rear/etc naming style
	   */
	  if ((i % 8) == 0)
	    if ((group = mixer_ext_create_group (dev, 0, "pcm")) < 0)
	      return group;

	  switch (i)
	    {
	    case 0:
	      strcpy (tmp, "main");
	      break;		/* Duplex device */
	    case 1:
	      strcpy (tmp, "front");
	      break;
	    case 2:
	      strcpy (tmp, "side");
	      break;
	    case 3:
	      strcpy (tmp, "C/L");
	      break;
	    case 4:
	      strcpy (tmp, "rear");
	      break;
	    }
	}


      if ((err = mixer_ext_create_control (dev, group, i, sblive_set_vol,
#ifdef TEST_3D
					   MIXT_3D,
#else
					   MIXT_SLIDER,
#endif
					   tmp,
					   100,
					   MIXF_PCMVOL | MIXF_READABLE | MIXF_WRITEABLE)) <
	  0)
	return err;
      if ((err = mixer_ext_create_control (dev, group,
					   i,
					   sblive_get_peak,
					   MIXT_STEREOPEAK,
					   "-", 144, MIXF_READABLE)) < 0)
	return err;
    }

  return 0;
}

static int
create_efx_mixer (int dev)
{
  sblive_devc *devc = mixer_devs[dev]->hw_devc;
  int group = 0, err = 0, i, mode;
  int group_created = 0;
  int typ, maxval;

  if (!devc->extinfo_loaded)
    {
      return 0;
    }

  if (devc->gpr == NULL)
    {
      return 0;
    }

  if (devc->mixer_group >= 0)
    mixer_ext_truncate (dev, devc->mixer_group);
  devc->mixer_group = -1;

  if (devc->gpr->ngpr >= MAX_GPR_PARMS)
    return OSS_EINVAL;

  for (i = 0; i < devc->gpr->ngpr; i++)
    {
      if (devc->gpr->gpr[i].num >= MAX_GPR)
	continue;

      typ = devc->gpr->gpr[i].type;

      if (typ == MIXT_GROUP)
	{
	  if ((group =
	       mixer_ext_create_group (dev, 0, devc->gpr->gpr[i].name)) < 0)
	    return group;

	  if (!group_created)
	    devc->mixer_group = group;
	  group_created = 1;
	  continue;
	}

#if 0
      if (!group_created)
	{
	  cmn_err (CE_WARN, "Mixer initialization sequence error\n");
	  return OSS_EINVAL;
	}
#endif
      mode = MIXF_READABLE;
      maxval = 144;

      switch (typ)
	{
	case EMU_MIXT_EQ1:
	case EMU_MIXT_EQ2:
	case EMU_MIXT_EQ3:
	case EMU_MIXT_EQ4:
	  {
	    mode |= MIXF_WRITEABLE;
	    maxval = 255;
	    typ = MIXT_SLIDER;
	  }
	  break;

	case MIXT_STEREOSLIDER:
	case MIXT_SLIDER:
	case MIXT_MONOSLIDER:
	  {
	    mode |= MIXF_WRITEABLE;
	    maxval = 100;
	  }
	  break;

	case MIXT_STEREOVU:
	  typ = MIXT_STEREOPEAK;
	  break;

	case MIXT_ONOFF:
	  {
	    mode |= MIXF_WRITEABLE;
	    maxval = 1;
	  }
	  break;
	}

      if (devc->gpr->gpr[i].name[0] == '_')
	{
	  /* Hidden control */
	  if (strcmp (devc->gpr->gpr[i].name, "_PASSTHROUGH") == 0)
	    {
	      int ctrl = devc->gpr->gpr[i].num;
	      devc->passthrough_gpr = ctrl;

	      sblive_write_reg (devc, ctrl + GPR0, 0, 1);
	      sblive_write_reg (devc, ctrl + GPR0 + 1, 0, 0);
	    }
	}
      else
	{
	  /* Visible control */
	  if ((err = mixer_ext_create_control (dev, group,
					       devc->gpr->gpr[i].num,
					       sblive_set_gpr, typ,
					       devc->gpr->gpr[i].name,
					       maxval, mode)) < 0)
	    return err;
	}

      if (!group_created)
	devc->mixer_group = err;
      group_created = 1;

      if (is_special_gpr (devc->gpr->gpr[i].num))
	{
	  sblive_set_gpr (dev, devc->gpr->gpr[i].num, SNDCTL_MIX_WRITE,
			  devc->gpr_values[devc->gpr->gpr[i].num]);
	}
      else
	{
	  sblive_set_gpr (dev, devc->gpr->gpr[i].num, SNDCTL_MIX_WRITE,
			  devc->gpr->gpr[i].def);
	}
    }
  return 0;
}

static int
mixer_ext_init (int dev)
{
  sblive_devc *devc = mixer_devs[dev]->hw_devc;

  devc->extinfo_loaded = 1;
  create_soft_mixer (dev);
  create_efx_mixer (dev);
  return 0;
}

static int
mixer_override (int dev, int audiodev, unsigned int cmd, int val)
{
  sblive_devc *devc = mixer_devs[dev]->hw_devc;
  switch (cmd)
    {
    case SOUND_MIXER_READ_VOLUME:
      return sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_READ, 0);
      break;

    case SOUND_MIXER_WRITE_VOLUME:
      return sblive_set_gpr (dev, GPR_VOLUME, SNDCTL_MIX_WRITE, val);
      break;

    case SOUND_MIXER_READ_PCM:
      if (audiodev >= 0 && audiodev < num_audio_engines)
	{
	  sblive_portc *portc = NULL;
	  int i;

	  for (i = 0; i < devc->n_audiodevs && portc == NULL; i++)
	    if (devc->portc[i].audiodev == audiodev)
	      portc = &devc->portc[i];

	  if (portc == NULL)
	    return OSS_EIO;

	  return portc->playvol | (portc->playvol << 8);
	}
      return sblive_set_gpr (dev, GPR_PCM, SNDCTL_MIX_READ, 0);
      break;

    case SOUND_MIXER_WRITE_PCM:
      if (audiodev >= 0 && audiodev < num_audio_engines)
	{
	  sblive_portc *portc = NULL;
	  int i, left, right;

	  for (i = 0; i < devc->n_audiodevs && portc == NULL; i++)
	    if (devc->portc[i].audiodev == audiodev)
	      portc = &devc->portc[i];

	  if (portc == NULL)
	    return OSS_EIO;

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

	  if (left < 0)
	    left = 0;
	  if (right < 0)
	    right = 0;
	  if (left > 100)
	    left = 100;
	  if (right > 100)
	    right = 100;

	  if (right > left)
	    left = right;
	  portc->playvol = left;
	  mixer_devs[devc->mixer_dev]->modify_counter++;	/* Force update of mixer */
	  update_output_device (devc, portc);

	  return portc->playvol | (portc->playvol << 8);
	}
      return sblive_set_gpr (dev, GPR_PCM, SNDCTL_MIX_WRITE, val);
      break;
    }

  return 0;
}

static const char *port_names[] =
  { "front out", "side out", "center/lfe out", "rear out" };

static const __inline__ char *
get_port_name (sblive_devc * devc, int n)
{
  int max_names = 3;

  if (devc->feature_mask & SB_AUDIGY)
    max_names = 3;
  if (devc->feature_mask & SB_LIVE)
    max_names = 2;

  n = n - 1;

  if (n > max_names)
    return "extra out";

  return port_names[n];
}

static void
unload_mpu (sblive_devc * devc)
{
  if (devc == NULL)
    return;

  if (devc->feature_mask & SB_AUDIGY)
    unload_audigyuart (devc);
  else
    uart401_disable (&devc->uart401devc);
}

int
oss_sblive_attach (oss_device_t * osdev)
{
  sblive_devc *devc;
  int i, err;
  int frontdev = -1, ndevs = 0;
  int first_dev = -1;
  unsigned char pci_irq_line, pci_revision /*, pci_latency */ ;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  unsigned int subvendor;
  adev_p adev;
  extern int sblive_devices;

  int audiodevs_to_create = sblive_devices;

  char tmp[64];

  DDB (cmn_err (CE_CONT, "sblive_attach entered\n"));

  pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
  pci_read_config_word (osdev, PCI_DEVICE_ID, &device);

  if (vendor != PCI_VENDOR_ID_CREATIVE)
    {
      cmn_err (CE_WARN, "Unrecognized SB live vendor %x\n", vendor);
      return 0;
    }

  if (device != PCI_DEVICE_ID_SBLIVE
      && device != PCI_DEVICE_ID_AUDIGY
      && device != PCI_DEVICE_ID_AUDIGY_CARDBUS
      && device != PCI_DEVICE_ID_AUDIGYVALUE)
    {
      cmn_err (CE_WARN, "Unrecognized SB live device %x:%x\n", vendor,
	       device);
      return 0;
    }

#ifdef AUDIGY_ONLY
  if (device == PCI_DEVICE_ID_SBLIVE)
    {
      cmn_err (CE_CONT,
	       "Error: Due to hardware limitations SB Live is not\n");
      cmn_err (CE_CONT, "supported under this hardware architecture.\n");
      cmn_err (CE_CONT,
	       "Consider upgrading to SB Audigy which is supported.\n");
      return 0;
    }
#endif

  pci_read_config_dword (osdev, 0x2c, &subvendor);
  pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision);
  pci_read_config_word (osdev, PCI_COMMAND, &pci_command);
  pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line);
  pci_read_config_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr);

  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO;
  pci_command &= ~(PCI_COMMAND_SERR | PCI_COMMAND_PARITY);
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);


  if (pci_ioaddr == 0)
    {
      cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n");
      return 0;
    }

  if (pci_irq_line == 0)
    {
      cmn_err (CE_WARN, "IRQ not assigned by BIOS.\n");
      return 0;
    }

  if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL)
    {
      cmn_err (CE_WARN, "Out of memory\n");
      return 0;
    }

  memset (devc, 0, sizeof (*devc));
  devc->osdev = osdev;
  osdev->devc = devc;
  MUTEX_INIT (osdev, devc->mutex, MH_DRV);
  MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1);

  devc->emu_page_shift = 1;	/* Default page shift */

  devc->card_name = "Generic SB Live!";
  devc->subvendor = subvendor;

  devc->min_audiodevs = 5;	/* Audigy supports 7.1 */

  if (device == PCI_DEVICE_ID_AUDIGYVALUE)
    {
      /* SOLWAY subvendor id is 0x10211103 */
      if ((devc->subvendor == 0x10211102) || (devc->subvendor == 0x10211103))
	devc->card_name = "SB Audigy4";
      else
	devc->card_name = "SB Audigy2 Value";
      devc->feature_mask = SB_AUDIGY | SB_AUDIGY2 | SB_AUDIGY2VAL;
    }
  else if (device == PCI_DEVICE_ID_AUDIGY)
    {
      if (devc->subvendor >= 0x10021102 && devc->subvendor <= 0x20051102)
	    {
      	      devc->card_name = "SB Audigy2";
      	      devc->feature_mask = SB_AUDIGY | SB_AUDIGY2;
	    }
	  else
	    {
      	      devc->card_name = "SB Audigy";
      	      devc->feature_mask = SB_AUDIGY;
	    }
    }
  else if (device == PCI_DEVICE_ID_AUDIGY_CARDBUS)
    {
      if (devc->subvendor >= 0x10021102 && devc->subvendor <= 0x20051102)
	{
	  devc->card_name = "SB Audigy2 ZS Notebook";
	  devc->feature_mask = SB_AUDIGY | SB_AUDIGY2;
	}
      else
	{
	  devc->card_name = "SB Audigy";
	  devc->feature_mask = SB_AUDIGY;
	}
      DDB (cmn_err (CE_CONT,
		    "emu10k2 chip rev %d, pcb rev %d\n", pci_revision,
		    sblive_read_reg (devc, 0x5f, 0)));
    }
  else
    {
      devc->card_name = "SB Live";
      devc->feature_mask = SB_LIVE;
      devc->min_audiodevs = 4;	/* Just 5.1 */
    }

  if (audiodevs_to_create < devc->min_audiodevs)
    audiodevs_to_create = devc->min_audiodevs;
  if (audiodevs_to_create > MAX_ADEV)
    audiodevs_to_create = MAX_ADEV;

  devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);
  devc->base &= ~0x3;

  devc->gpr = NULL;
  oss_register_device (osdev, devc->card_name);

  devc->irq = pci_irq_line;

  devc->page_map = NULL;
  devc->vpage_map = NULL;
  devc->nr_pages = 0;
  devc->max_pages = 0;
  devc->max_mem = 0;
  devc->silent_page = NULL;
  devc->subvendor = subvendor;
  devc->passthrough_gpr = -1;

  if ((err = oss_register_interrupts (devc->osdev, 0, sbliveintr, NULL)) < 0)
    {
      cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err);
      return 0;
    }

  devc->mixer_group = -1;
  devc->extinfo_loaded = 0;
  devc->autoreset = 1;
  devc->speaker_mode = SMODE_FRONTREAR;

/*
 * Init mixer
 */
  devc->mixer_dev =
    ac97_install (&devc->ac97devc, devc->card_name, ac97_read, ac97_write,
		  devc, devc->osdev);
  if (devc->mixer_dev < 0)
    {
      cmn_err (CE_WARN, "Mixer install failed - cannot continue\n");
      return 0;
    }

  devc->ac97devc.mixer_ext = 0;
  devc->ac97devc.spdifout_support = 0;
  devc->ac97devc.spdifin_support = 0;
  if (ac97_init_ext
      (devc->mixer_dev, &devc->ac97devc, mixer_ext_init, 100) < 0)
    {
      cmn_err (CE_WARN, "Mixer ext install failed\n");
    }

  /* first set the AC97 PCM to max - otherwise sound is too low */
  ac97_mixer_set (&devc->ac97devc, SOUND_MIXER_PCM, 100 | (100 << 8));

  ac97_remove_control (&devc->ac97devc, BOGUS_MIXER_CONTROLS, 0);
  ac97_override_control (&devc->ac97devc, SOUND_MIXER_VOLUME,
			 mixer_override, 100 | (100 << 8));
  ac97_override_control (&devc->ac97devc, SOUND_MIXER_PCM,
			 mixer_override, 100 | (100 << 8));

  attach_mpu (devc);

/*
 * Audio initialization
 */
  init_emu10k1 (devc);

  for (i = 0; i < audiodevs_to_create; i++)
    {
      sblive_portc *portc = &devc->portc[i];
      int caps = ADEV_AUTOMODE;
      int fmts = 0;
      devc->n_audiodevs = i + 1;

      portc->memptr = devc->audio_memptr;
      devc->audio_memptr += (DMABUF_SIZE + 4095) & ~4095;

      if (devc->audio_memptr > AUDIO_MEMSIZE)
	{
	  cmn_err (CE_WARN, "Audio memory block exhausted (%d/%d)\n",
		   devc->audio_memptr, AUDIO_MEMSIZE);
	  return OSS_ENOSPC;
	}

      if (i == 0)
	{
	  strcpy (tmp, devc->card_name);
	  sprintf (tmp, "%s main", devc->card_name);
	  caps |= ADEV_DUPLEX;
	}
      else
	{
	  sprintf (tmp, "%s %s", devc->card_name, get_port_name (devc, i));
	  caps |= ADEV_NOINPUT;
#if 0
	  if (i >= devc->min_audiodevs)
	    caps |= ADEV_HWMIX;
#endif
	  if (i >= devc->min_audiodevs + 1)
	    caps |= ADEV_SHADOW;
	}
      if ((devc->feature_mask & SB_AUDIGY) && i == audiodevs_to_create - 1)
	{
	  sprintf (tmp, "%s raw S/PDIF (output only)", devc->card_name);
	  caps &= ~(ADEV_SHADOW /* | ADEV_HWMIX*/);
	  caps |= ADEV_SPECIAL;
	  fmts |= AFMT_AC3;
	}
#if 0
      if (devc->feature_mask & SB_AUDIGY)
	caps |= ADEV_COLD;
#endif
      if ((portc->audiodev =
	   oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
				 devc->osdev,
				 devc->osdev,
				 tmp,
				 &sblive_audio_driver,
				 sizeof (audiodrv_t), caps,
				 fmts | AFMT_U8 | AFMT_S16_LE, devc, -1)) < 0)
	{
	  portc->audiodev = -1;
	  return (i > 0);
	}
      else
	{
	  int x;

	  adev = audio_engines[portc->audiodev];
	  adev->nrates=0;
	  for (x = 0; speed_tab[x].speed != 0; x++)
	    adev->rates[adev->nrates++] = speed_tab[x].speed;

	  if (i == 0)
	    first_dev = portc->audiodev;
	  adev->devc = devc;
	  adev->portc = portc;
	  adev->rate_source = first_dev;
	  adev->mixer_dev = devc->mixer_dev;
	  adev->min_rate = 8000;
	  adev->max_rate = 48000;

	  if (!(devc->feature_mask & SB_AUDIGY))
	    {
	      /*
	       * SB Live supports only 31 PCI address bits
	       */
	      adev->dmabuf_maxaddr = MEMLIMIT_31BITS;
	    }

	  portc->mode = 0;
	  adev->oformat_mask |= AFMT_AC3;
	  portc->input_type = ITYPE_ANALOG;
	  if ((devc->feature_mask & SB_AUDIGY) && i == audiodevs_to_create - 1)
	    portc->input_type = ITYPE_SPDIF;
	  if (i == 1)
	    frontdev = portc->audiodev;
	  if (i > 0)
	    ndevs++;

	  portc->playvol = 100;
	  portc->playangle = 0;
	  portc->playdist = 50;
	  portc->vu_left = 0;
	  portc->vu_right = 0;
	  portc->audio_active = 0;
	  portc->voice_chn = i * 2;
	  portc->port_number = i;
	  devc->voice_busy[i * 2] = 1;
	  devc->voice_busy[i * 2 + 1] = 1;
	  portc->resetvol = 0;
	  if (devc->feature_mask & SB_LIVE)
	    {
/*
 * Do not enable vmix by default on Live! It would cause enormous 
 * latencies because emu10k1 doesn't have working full/half buffer DMA
 * interrupts.
 */
	      adev->vmix_flags = VMIX_MULTIFRAG;
	      adev->max_intrate = 50;
	      adev->min_block = 4096;
	    }
	  else
	    {
	      adev->max_fragments = 2;
	    }

	  /*
	   * Hide vmix main volume control and peak meters if no 
	   * real HW mixing devices are enabled.
	   */
#if 0
	  if (audiodevs_to_create <= devc->min_audiodevs)
	    adev->vmix_flags |= VMIX_NOMAINVOL;
#endif
	  adev->iformat_mask = AFMT_S16_LE;	/* No 8 bit recording */

	  if (i == 0)
	    {
	      if (devc->feature_mask & SB_LIVE)
		adev->magic = EMU10K1_MAGIC;
	      else
		adev->magic = EMU10K2_MAGIC;
	    }
#ifdef CONFIG_OSS_VMIX
          if (i == 0)
            vmix_attach_audiodev(devc->osdev, first_dev, -1, 0);
#endif
	}
      adev->mixer_dev = devc->mixer_dev;
    }

#ifdef USE_REMUX
  /* Install Remux (only 5.1 support for the time being) */
  sprintf (tmp, "%s 5.1 output device", devc->card_name);
  if (frontdev > 0 && ndevs >= 3)	/* Have enough devices for 5.1 */
    remux_install (tmp, devc->osdev, frontdev, frontdev + 1, frontdev + 2,
		   -1);
#endif

#ifndef NO_EMU10K1_SYNTH
  sblive_install_synth (devc);
#endif

  touch_mixer (devc->mixer_dev);
  init_effects (devc);

  return 1;
}

int
oss_sblive_detach (oss_device_t * osdev)
{
  sblive_devc *devc;

  if (oss_disable_device (osdev) < 0)
    return 0;

  devc = osdev->devc;

  OUTL (devc->osdev, 0, devc->base + 0x0c);	/* Intr enable (all off) */
  OUTL (devc->osdev,
	HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
	HCFG_MUTEBUTTONENABLE, devc->base + 0x14);

  sblive_write_reg (devc, ADCSR, 0, 0x0);
  sblive_write_reg (devc, ADCBA, 0, 0x0);
  sblive_write_reg (devc, ADCBA, 0, 0x0);

  sblive_write_reg (devc, PTBA, 0, 0);

#ifndef NO_EMU10K1_SYNTH
  sblive_remove_synth (devc);
#endif
  if (devc->page_map != NULL)
    CONTIG_FREE (devc->osdev, devc->page_map, devc->max_pages * 4, devc->page_map_dma_handle);
  if (devc->vpage_map != NULL)
    KERNEL_FREE (devc->vpage_map);
  if (devc->silent_page != NULL)
    CONTIG_FREE (devc->osdev, devc->silent_page, 4096, devc->silent_page_dma_handle);
  devc->max_pages = 0;
  devc->max_mem = 0;
  devc->page_map = NULL;
  devc->vpage_map = NULL;
  devc->silent_page = NULL;
  unload_mpu (devc);

  oss_unregister_interrupts (devc->osdev);

  MUTEX_CLEANUP (devc->mutex);
  MUTEX_CLEANUP (devc->low_mutex);
  UNMAP_PCI_IOADDR (devc->osdev, 0);
  oss_unregister_device (osdev);

  return 1;
}
