Google
 

Open Sound System
The Hitchhiker's Guide to OSS 4.1 Internals

Do you have problems with sound/audio application development? Don't panic! Click here for help!

oss_sblive/oss_sblive.c

Driver for Creative SB Live/Audigy/2/4. Audio, MIDI and mixer services.

Description




This file is part of Open Sound System.

Copyright (C) 4Front Technologies 1996-2008.

This this source file is released under GPL v2 license (no other versions). 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)

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;
}

Copyright (C) 4Front Technologies, 2007. All rights reserved.

Back to index OSS web site


Copyright (C) 4Front Technologies, 2007. All rights reserved.
Back to index OSS web site