/*
 * Purpose: Driver for Creative Audigy LS audio controller
 *
 * This sound card has been sold under many different names.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#include "audigyls_cfg.h"
#include "oss_pci.h"
#include "ac97.h"
#include "midi_core.h"
#include "remux.h"

#define DEFAULT_RATE 48000

#undef  USE_ITIMER

#define PCI_VENDOR_ID_CREATIVE 		0x1102
#define PCI_DEVICE_ID_CREATIVE_AUDIGYLS 0x0007

/*
 * Indirect registers
 */

#define PTBA	0x000
#define PTBS	0x001
#define PTCA	0x002
#define PFBA	0x004
#define PFBS	0x005
#define CPFA	0x006
#define PFEA	0x007
#define CPCAV	0x008
#define RFBA	0x010
#define RFBS	0x011
#define CRFA	0x012
#define CRCAV	0x013
#define CDL	0x020
#define SA	0x040
#define SCS3	0x041
#define SCS0	0x042
#define SCS1	0x043
#define SCS2	0x044
#define SPC	0x045
#define WMARK	0x046
#define SPSC	0x049
#define RCD	0x050		/* 0x50-0z5f */
#define P17RECSEL       0x060
#define P17RECVOLL      0x061
#define P17RECVOLH      0x062

#define HMIXMAP_SPDIF 0x63
#define SMIXMAP_SPDIF 0x64
#define MIXCTL_SPDIF 0x65
#define MIXVOL_SPDIF 0x66
#define HMIXMAP_I2S 0x67
#define SMIXMAP_I2S 0x68
#define MIXCTL_I2S 0x69
#define MIXVOL_I2S 0x6a

/* MIDI UART */
#define MUDATA	0x06c
#define MUCMDA	0x06d
#define MUDATB	0x06e
#define MUCMDB	0x06f

#define SRT	0x070
#define SRCTL	0x071
#define AUDCTL	0x072
#define CHIP_ID	0x074
#define AINT_ENABLE	0x075
#define AINT_STATUS	0x076
#define Wall192		0x077
#define Wall441		0x078
#define IT		0x079
#define SPI		0x07a
#define I2C_A		0x07b
#define I2C_0		0x07c
#define I2C_1		0x07d

/*
 * Global Interrupt bits
 */

#define IE 0x0c
#define INTR_PCI	(1<<  0)
#define INTR_TXA	(1<<  1)
#define INTR_RXA	(1<<  2)
#define INTR_IT1	(1<<  3)
#define INTR_IT2	(1<<  4)
#define INTR_SS_	(1<<  5)
#define INTR_SRT	(1<<  6)
#define INTR_GP 	(1<<  7)
#define INTR_AI 	(1<<  8)
#define INTR_I2Cdac 	(1<<  9)
#define INTR_I2CEE 	(1<< 10)
#define INTR_SPI 	(1<< 11)
#define INTR_SPF 	(1<< 12)
#define INTR_SUO 	(1<< 13)
#define INTR_SUI 	(1<< 14)
#define INTR_TXB 	(1<< 16)
#define INTR_RXB 	(1<< 17)

/*
 * Audio interrupt bits
 */

#define AI_PFH		(1<<  0)
#define AI_PFF		(1<<  4)
#define AI_TFH		(1<<  8)
#define AI_TFF		(1<< 12)
#define AI_RFH		(1<< 16)
#define AI_RFF		(1<< 20)
#define AI_EAI		(1<< 24)


#define MAX_PORTC	4

extern int audigyls_spdif_enable;

typedef struct
{
  int audio_dev;
  int play_port;
  int rec_port;
  int open_mode;
  int trigger_bits;
  int audio_enabled;
  int channels;
  int bits;
  int speed;
#ifdef USE_ITIMER
  int frag_192khz;
#endif
  int port_type;
  int play_ptr, rec_ptr;
}
audigyls_portc;

typedef struct
{
  oss_device_t *osdev;
  oss_native_word base;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;
  int open_mode;
  char *card_name;
  unsigned int subvendor;
  int rec_src;			/* record channel src spdif/i2s/ac97/src */
#define RECSEL_SPDIFOUT   0
#define RECSEL_I2SOUT     1
#define RECSEL_SPDIFIN    2
#define RECSEL_I2SIN      3
#define RECSEL_AC97       4
#define RECSEL_SRC        5

  int mixer_dev;
  ac97_devc ac97devc;
  int has_ac97;
  int spread;			/* copy front to surr/center channels */
  int loopback;			/* record channel input from /dev/dspXX */
  int input_source;		/* input from mic/line/aux/etc */
/*
 * UART
 */
  oss_midi_inputbyte_t midi_input_intr;
  int midi_opened, midi_disabled;
  volatile unsigned char input_byte;
  int midi_dev;
  int mpu_attached;
  int playvol[4];
  int recvol;
  audigyls_portc portc[MAX_PORTC];

}
audigyls_devc;


static void audigylsuartintr (audigyls_devc * devc);

static unsigned int
read_reg (audigyls_devc * devc, int reg, int chn)
{
  oss_native_word flags;
  unsigned int val;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTL (devc->osdev, (reg << 16) | (chn & 0xffff), devc->base + 0x00);	/* Pointer */
  val = INL (devc->osdev, devc->base + 0x04);	/* Data */
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

/*printk("Read reg %03x (ch %d) = %08x\n", reg, chn, val);*/
  return val;
}

static void
write_reg (audigyls_devc * devc, int reg, int chn, unsigned int value)
{
  oss_native_word flags;

  /*printk("Write reg %03x (ch %d) = %08x\n", reg, chn, value); */
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTL (devc->osdev, (reg << 16) | (chn & 0xffff), devc->base + 0x00);	/* Pointer */
  OUTL (devc->osdev, value, devc->base + 0x04);	/* Data */
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
#if 0
  {
    char tmp[100];
    int ok = 1;
    sprintf (tmp, "@w%d %04x/%s %x", chn, reg, emu_regname (reg, &ok), value);
    if (ok)
      oss_do_timing (tmp);
  }
#endif
}


static int
audigyls_ac97_read (void *devc_, int wAddr)
{
  audigyls_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 == 10000)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return -EIO;
    }
  dtemp = INW (devc->osdev, devc->base + 0x1c);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return dtemp & 0xffff;
}

static int
audigyls_ac97_write (void *devc_, int wAddr, int wData)
{
  audigyls_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;
  if (i == 10000)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return -EIO;
    }
  OUTW (devc->osdev, wData, devc->base + 0x1c);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return 0;
}

static void
check_recording_intr (audigyls_devc * devc, audigyls_portc * portc)
{
#if 1
  int pos, n = 0;
  dmap_p dmap;

  dmap = audio_engines[portc->audio_dev]->dmap_in;

  pos = read_reg (devc, CRFA, portc->rec_port);
  pos /= dmap->fragment_size;
  while (dmap_get_qtail (dmap) != pos && n++ < dmap->nfrags)
#endif
    oss_audio_inputintr (portc->audio_dev, 0);
}

static void
check_playback_intr (audigyls_devc * devc, audigyls_portc * portc)
{
#if 1
  int pos, n = 0;
  dmap_p dmap;

  dmap = audio_engines[portc->audio_dev]->dmap_out;

  pos = read_reg (devc, CPFA, portc->play_port);
  pos /= dmap->fragment_size;
  while (dmap_get_qhead (dmap) != pos && n++ < dmap->nfrags)
#endif
    oss_audio_outputintr (portc->audio_dev, 0);
}

static int
audigylsintr (oss_device_t * osdev)
{
  int serviced = 0;
  unsigned int status;
  unsigned int astatus = 0;
  int i;
  audigyls_devc *devc = osdev->devc;
  audigyls_portc *portc;
  oss_native_word flags;

  flags = 0;			/* To fix compiler warnings about unused variable */
  MUTEX_ENTER (devc->mutex, flags);

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

  if (status & INTR_RXA)	/* MIDI RX interrupt */
    {
      audigylsuartintr (devc);
    }

  if (status & INTR_AI)
    {
      astatus = read_reg (devc, AINT_STATUS, 0);
      for (i = 0; i < MAX_PORTC; i++)
	{
	  portc = &devc->portc[i];

	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT) &&
	      (astatus & ((AI_PFF | AI_PFH) << portc->play_port)))
	    {
	      dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_out;

	      if (astatus & (AI_PFF << portc->play_port))
		portc->play_ptr = 0;
	      if (astatus & (AI_PFH << portc->play_port))
		portc->play_ptr = dmap->bytes_in_use / 2;

	      oss_audio_outputintr (portc->audio_dev, 0);
	    }
	  if ((portc->trigger_bits & PCM_ENABLE_INPUT) &&
	      (astatus & ((AI_RFF | AI_RFH) << portc->rec_port)))
	    {
	      dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_in;

	      if (astatus & (AI_RFF << portc->rec_port))
		portc->rec_ptr = 0;
	      if (astatus & (AI_RFH << portc->rec_port))
		portc->rec_ptr = dmap->bytes_in_use / 2;

	      oss_audio_inputintr (portc->audio_dev, 0);
	    }
	}
      write_reg (devc, AINT_STATUS, 0, astatus);
    }

  if (status & INTR_IT1)
    {
      for (i = 0; i < MAX_PORTC; i++)
	{
	  portc = &devc->portc[i];

	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    check_playback_intr (devc, portc);

	  if ((portc->trigger_bits & PCM_ENABLE_INPUT))
	    check_recording_intr (devc, portc);
	}
    }

  serviced = 1;
  OUTL (devc->osdev, status, devc->base + 0x08);	/* Acknowledge */
  MUTEX_EXIT (devc->mutex, flags);
  return serviced;
}

static int
audigyls_set_rate (int dev, int arg)
{
  audigyls_portc *portc = audio_engines[dev]->portc;

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

  if (audio_engines[dev]->flags & ADEV_FIXEDRATE)
    arg = DEFAULT_RATE;

  if (arg != 44100 && arg != 48000 && arg != 96000 && arg != 192000)
    arg = 48000;

  portc->speed = arg;
  return portc->speed;
}

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

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

  if ((arg == 1))
    arg = 2;

  if (portc->open_mode & OPEN_READ)
    return portc->channels = 2;

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

static unsigned int
audigyls_set_format (int dev, unsigned int arg)
{
  audigyls_portc *portc = audio_engines[dev]->portc;

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

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

  return portc->bits = arg;
}

/*ARGSUSED*/
static int
audigyls_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static void audigyls_trigger (int dev, int state);

static void
audigyls_reset (int dev)
{
  audigyls_trigger (dev, 0);
}

/*ARGSUSED*/
static int
audigyls_open (int dev, int mode, int open_flags)
{
  audigyls_portc *portc = audio_engines[dev]->portc;
  audigyls_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;

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

  if (devc->open_mode & mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return -EBUSY;
    }

  devc->open_mode |= mode;
  portc->open_mode = mode;
  portc->audio_enabled = ~mode;
  portc->play_ptr = portc->rec_ptr = 0;

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

static void
audigyls_close (int dev, int mode)
{
  audigyls_portc *portc = audio_engines[dev]->portc;
  audigyls_devc *devc = audio_engines[dev]->devc;

  audigyls_reset (dev);
  portc->open_mode = 0;
  portc->audio_enabled &= ~mode;
  devc->open_mode &= ~mode;
}

/*ARGSUSED*/
static void
audigyls_output_block (int dev, oss_native_word buf, int count, int fragsize,
		       int intrflag)
{
}

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

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

#ifdef USE_ITIMER
static void
check_itimer (audigyls_devc * devc)
{
  int i;
  unsigned int t = 0x1fffffff;
  audigyls_portc *portc;
  int tmp;

  for (i = 0; i < MAX_PORTC; i++)
    {
      portc = &devc->portc[i];
      if (portc->frag_192khz != 0 && portc->frag_192khz < t)
	t = portc->frag_192khz;
    }
  if (t == 0x1fffffff)		/* No audio devices active */
    {
      tmp = INL (devc->osdev, devc->base + IE);
      tmp &= ~INTR_IT1;
      OUTL (devc->osdev, tmp, devc->base + IE);
    }
  else
    {
      t /= 16;
      if (t < 1)
	t = 1;
      write_reg (devc, IT, 0, t);
      tmp = INL (devc->osdev, devc->base + IE);
      tmp |= INTR_IT1;
      OUTL (devc->osdev, tmp, devc->base + IE);
    }
}

static void
adjust_itimer (audigyls_devc * devc, audigyls_portc * portc, dmap_p dmap)
{
  unsigned int t;

  /* Compute byte rate */

  t = portc->speed * portc->channels;

  switch (portc->bits)
    {
    case AFMT_S16_LE:
    case AFMT_S16_BE:
    case AFMT_AC3:
      t *= 2;
      break;

    case AFMT_S32_LE:
    case AFMT_S32_BE:
    case AFMT_S24_LE:
    case AFMT_S24_BE:
      t *= 4;
      break;
    }

/* Compute the number of 192kHz ticks per fragment */

  t = (dmap->fragment_size * 192000) / t;	/* msecs / fragment */
  if (t < 1)
    t = 1;

  portc->frag_192khz = t;
  check_itimer (devc);
}
#endif

static void
audigyls_trigger (int dev, int state)
{
  audigyls_devc *devc = audio_engines[dev]->devc;
  audigyls_portc *portc = audio_engines[dev]->portc;
  int tmp;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      tmp = read_reg (devc, SA, 0);
	      tmp |= 1 << portc->play_port;
	      write_reg (devc, SA, 0, tmp);
#ifdef USE_ITIMER
	      check_itimer (devc);
#else
	      tmp = read_reg (devc, AINT_ENABLE, 0);
	      tmp |= ((AI_PFH | AI_PFF) << portc->play_port);
	      write_reg (devc, AINT_ENABLE, 0, tmp);
#endif
	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	    }
	}

      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
#ifdef USE_ITIMER
	      portc->frag_192khz = 0;
	      check_itimer (devc);
#endif
	      /* Disable Play channel */
	      tmp = read_reg (devc, SA, 0);
	      tmp &= ~(1 << portc->play_port);
	      write_reg (devc, SA, 0, tmp);
#ifndef USE_ITIMER
	      tmp = read_reg (devc, AINT_ENABLE, 0);
	      tmp &= ~((AI_PFH | AI_PFF) << portc->play_port);
	      write_reg (devc, AINT_ENABLE, 0, tmp);
#endif
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      /* Enable Rec Channel */
	      tmp = read_reg (devc, SA, 0);
	      tmp |= 0x100 << portc->rec_port;	/* enable record */
	      write_reg (devc, SA, 0, tmp);
#ifdef USE_ITIMER
	      check_itimer (devc);
#else
	      tmp = read_reg (devc, AINT_ENABLE, 0);
	      tmp |= ((AI_RFF | AI_RFH) << portc->rec_port);
	      write_reg (devc, AINT_ENABLE, 0, tmp);
#endif
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_INPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_INPUT;
#ifdef USE_ITIMER
	      portc->frag_192khz = 0;
	      check_itimer (devc);
#endif
	      /* disable channel */
	      tmp = read_reg (devc, SA, 0);
	      tmp &= ~(0x100 << portc->rec_port);
	      write_reg (devc, SA, 0, tmp);
#ifndef USE_ITIMER
	      tmp = read_reg (devc, AINT_ENABLE, 0);
	      tmp &= ~((AI_RFF | AI_RFH) << portc->rec_port);
	      write_reg (devc, AINT_ENABLE, 0, tmp);
#endif
	    }
	}
    }

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
audigyls_prepare_for_input (int dev, int bsize, int bcount)
{
  unsigned int tmp, recmap, reg;
   /*LINTED*/ unsigned int oversample;
  audigyls_devc *devc = audio_engines[dev]->devc;
  audigyls_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_in;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  write_reg (devc, CRFA, portc->rec_port, 0);
  write_reg (devc, CRCAV, portc->rec_port, 0);

  write_reg (devc, RFBA, portc->rec_port, dmap->dmabuf_phys);
  write_reg (devc, RFBS, portc->rec_port, (dmap->bytes_in_use) << 16);

  /* set 16/24 bits */
  tmp = INL (devc->osdev, devc->base + 0x14);
  if (portc->bits == AFMT_S16_LE)
    tmp &= ~0x400;		/*16 bit */
  else
    tmp |= 0x400;		/*24 bit */
  OUTL (devc->osdev, tmp, devc->base + 0x14);

  /* set recording speed */
  reg = read_reg (devc, SRCTL, 0) & ~0xc000;

  switch (portc->speed)
    {
    case 48000:
      reg |= 0x0;
      oversample = 0x2;
      break;

    case 96000:
      reg |= 0x8000;
      oversample = 0xa;
      break;

    case 192000:
      reg |= 0xc000;
      oversample = 0xa;
      break;

    default:
      reg |= 0;
      oversample = 0x2;
      break;
    }

  write_reg (devc, SRCTL, 0, reg);
/*  audigyls_i2c_write(devc, 0xc, oversample);*/

/* setup record input */
  if (devc->loopback)
    {
      devc->rec_src = RECSEL_I2SOUT;
      recmap = 0;
    }
  else
    {
      if (devc->has_ac97)
	{
	  devc->rec_src = RECSEL_AC97;	/* audigy LS */
	}
      else
	{
	  devc->rec_src = RECSEL_I2SIN;	/* sb 7.1 value */
	}
      recmap = 0xe4;
    }
  tmp = recmap;			/* default record input map */
  tmp |= devc->rec_src << (16 + portc->rec_port * 3);	/* Select input */
  write_reg (devc, P17RECSEL, 0, tmp);
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;

#ifdef USE_ITIMER
  adjust_itimer (devc, portc, dmap);
#endif

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
audigyls_prepare_for_output (int dev, int bsize, int bcount)
{
  audigyls_devc *devc = audio_engines[dev]->devc;
  audigyls_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_out;
  unsigned int tmp, reg;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->bits == AFMT_AC3)
    portc->channels = 2;

  write_reg (devc, PTBA, portc->play_port, 0);
  write_reg (devc, PTBS, portc->play_port, 0);
  write_reg (devc, PTCA, portc->play_port, 0);

  write_reg (devc, CPFA, portc->play_port, 0);
  write_reg (devc, PFEA, portc->play_port, 0);
  write_reg (devc, CPCAV, portc->play_port, 0);


  /* set 16/24 bits */
  tmp = INL (devc->osdev, devc->base + 0x14);
  if (portc->bits == AFMT_S16_LE)
    tmp &= ~0x800;		/*16 bit */
  else
    tmp |= 0x800;		/*24 bit */
  OUTL (devc->osdev, tmp, devc->base + 0x14);

  /* set playback rate */
  tmp = read_reg (devc, SA, 0) & ~0xff0000;
  reg = read_reg (devc, SRCTL, 0) & ~0x0303000f;
#if 0
  switch (portc->speed)
    {
    case 48000:
      tmp |= 0;
      reg |= 0;
      break;

    case 44100:
      tmp |= 0x10000;
      reg |= 0x01010005;
      break;

    case 96000:
      tmp |= 0x20000;
      reg |= 0x0202000a;
      break;

    case 192000:
      tmp |= 0x30000;
      reg |= 0x0303000f;
      break;

    default:
      tmp |= 0;			/* default is 48000 */
      reg |= 0;
      break;
    }
#endif
  write_reg (devc, SA, 0, tmp << (portc->play_port * 2));
  write_reg (devc, SRCTL, 0, reg);

  /* Single buffering mode */
  write_reg (devc, PFBA, portc->play_port, dmap->dmabuf_phys);
  write_reg (devc, PFBS, portc->play_port, (dmap->bytes_in_use) << 16);

  if (audigyls_spdif_enable)
    {
      if (portc->bits == AFMT_AC3)
	{
	  audigyls_ac97_write (devc, 0x1c, 0x8000);
	  write_reg (devc, SCS3, 0, 0x02108006);	/* Non Audio */
#if 0
	  write_reg (devc, SCS0, 0, 0x02108006);	/* Non Audio */
	  write_reg (devc, SCS1, 0, 0x02108006);	/* Non Audio */
	  write_reg (devc, SCS2, 0, 0x02108006);	/* Non Audio */
#endif
	}
      else
	{
	  write_reg (devc, SCS3, 0, 0x02108004);	/* Audio */
#if 0
	  write_reg (devc, SCS0, 0, 0x02108004);	/* Audio */
	  write_reg (devc, SCS1, 0, 0x02108004);	/* Audio */
	  write_reg (devc, SCS2, 0, 0x02108004);	/* Audio */
#endif
	}
    }
  portc->audio_enabled |= PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;

#ifdef USE_ITIMER
  adjust_itimer (devc, portc, dmap);
#endif

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

static int
audigyls_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  unsigned int p = 0;
  audigyls_portc *portc = audio_engines[dev]->portc;

#if 1
  audigyls_devc *devc = audio_engines[dev]->devc;

  if (direction == PCM_ENABLE_OUTPUT)
    {
      p = read_reg (devc, CPFA, portc->play_port);
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      p = read_reg (devc, CRFA, portc->rec_port);
    }

  /*
   * Round to the nearest fragment boundary.
   */
  p = (p + dmap->fragment_size / 2);
  p = (p / dmap->fragment_size) * dmap->fragment_size;
#else
  if (direction == PCM_ENABLE_OUTPUT)
    {
      return portc->play_ptr;
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      return portc->rec_ptr;
    }
#endif

  return p % dmap->bytes_in_use;
}

static audiodrv_t audigyls_audio_driver = {
  audigyls_open,
  audigyls_close,
  audigyls_output_block,
  audigyls_start_input,
  audigyls_ioctl,
  audigyls_prepare_for_input,
  audigyls_prepare_for_output,
  audigyls_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  audigyls_trigger,
  audigyls_set_rate,
  audigyls_set_format,
  audigyls_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* audigyls_alloc_buffer, */
  NULL,				/* audigyls_free_buffer */
  NULL,
  NULL,
  audigyls_get_buffer_pointer
};


static __inline__ int
audigylsuart_status (audigyls_devc * devc)
{
  return read_reg (devc, MUCMDA, 0);
}

#define input_avail(devc) (!(audigylsuart_status(devc)&INPUT_AVAIL))
#define output_ready(devc)      (!(audigylsuart_status(devc)&OUTPUT_READY))
static void
audigylsuart_cmd (audigyls_devc * devc, unsigned char cmd)
{
  write_reg (devc, MUCMDA, 0, cmd);
}

static __inline__ int
audigylsuart_read (audigyls_devc * devc)
{
  return read_reg (devc, MUDATA, 0);
}

static __inline__ void
audigylsuart_write (audigyls_devc * devc, unsigned char byte)
{
  write_reg (devc, MUDATA, 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_audigylsuart (audigyls_devc * devc);
static void enter_uart_mode (audigyls_devc * devc);

static void
audigylsuart_input_loop (audigyls_devc * devc)
{
  while (input_avail (devc))
    {
      unsigned char c = audigylsuart_read (devc);

      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
audigylsuartintr (audigyls_devc * devc)
{
  audigylsuart_input_loop (devc);
}

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

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

  while (input_avail (devc))
    audigylsuart_read (devc);

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

  return 0;
}

/*ARGSUSED*/
static void
audigylsuart_close (int dev, int mode)
{
  audigyls_devc *devc = (audigyls_devc *) midi_devs[dev]->devc;
  reset_audigylsuart (devc);
  oss_udelay (10);
  enter_uart_mode (devc);
  reset_audigylsuart (devc);
  devc->midi_opened = 0;
}


static int
audigylsuart_out (int dev, unsigned char midi_byte)
{
  int timeout;
  audigyls_devc *devc = (audigyls_devc *) midi_devs[dev]->devc;
  oss_native_word flags;

  /*
   * Test for input since pending input seems to block the output.
   */

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (input_avail (devc))
    audigylsuart_input_loop (devc);

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  /*
   * Sometimes it takes about 130000 loops before the output becomes ready
   * (After reset). Normally it takes just about 10 loops.
   */

  for (timeout = 130000; timeout > 0 && !output_ready (devc); timeout--);

  if (!output_ready (devc))
    {
      cmn_err (CE_WARN, "UART timeout - Device not responding\n");
      devc->midi_disabled = 1;
      reset_audigylsuart (devc);
      enter_uart_mode (devc);
      return 1;
    }

  audigylsuart_write (devc, midi_byte);
  return 1;
}

/*ARGSUSED*/
static int
audigylsuart_ioctl (int dev, unsigned cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static midi_driver_t audigyls_midi_driver = {
  audigylsuart_open,
  audigylsuart_close,
  audigylsuart_ioctl,
  audigylsuart_out,
};

static void
enter_uart_mode (audigyls_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;
  audigylsuart_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 (audigylsuart_read (devc) == MPU_ACK)
	ok = 1;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}


void
attach_audigylsuart (audigyls_devc * devc)
{
  enter_uart_mode (devc);
  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "AUDIGYLS", "AudigyLS UART", &audigyls_midi_driver, sizeof (midi_driver_t), NULL,	/* &std_midi_synth, */
					0, devc, devc->osdev);
  devc->midi_opened = 0;
}

static int
reset_audigylsuart (audigyls_devc * devc)
{
  int ok, timeout, n;

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

  ok = 0;

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

      devc->input_byte = 0;
      audigylsuart_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 (audigylsuart_read (devc) == MPU_ACK)
	    ok = 1;

    }



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

  return ok;
}


int
probe_audigylsuart (audigyls_devc * devc)
{
  int ok = 0;
  oss_native_word flags;

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

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

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

  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", audigylsuart_status (devc)));
    }

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

void
unload_audigylsuart (audigyls_devc * devc)
{
  reset_audigylsuart (devc);
}


static void
attach_mpu (audigyls_devc * devc)
{
  devc->mpu_attached = 1;
  attach_audigylsuart (devc);
}

/* only for SBLive 7.1 */
int
audigyls_i2c_write (audigyls_devc * devc, int reg, int data)
{
  int i, timeout, tmp;


  tmp = (reg << 9 | data) << 16;	/* set the upper 16 bits */
  /* first write the command to the data reg */
  write_reg (devc, I2C_1, 0, tmp);
  for (i = 0; i < 20; i++)
    {
      tmp = read_reg (devc, I2C_A, 0) & ~0x6fe;
      /* see audigyls.pdf for bits */
      tmp |= 0x400 | 0x100 | 0x34;
      write_reg (devc, I2C_A, 0, tmp);
      /* now wait till controller sets valid bit (0x100) to 0 */
      timeout = 0;
       /*LINTED*/ while (1)
	{
	  tmp = read_reg (devc, I2C_A, 0);
	  if ((tmp & 0x100) == 0)
	    break;

	  if (timeout > 100)
	    break;

	  timeout++;
	}

      /* transaction aborted */
      if (tmp & 0x200)
	return 0;
    }
  return 1;
}

int
audigyls_spi_write (audigyls_devc * devc, int data)
{
  unsigned int orig;
  unsigned int tmp;
  int i, valid;

  tmp = read_reg (devc, SPI, 0);
  orig = (tmp & ~0x3ffff) | 0x30000;
  write_reg (devc, SPI, 0, orig | data);
  valid = 0;
  /* Wait for status bit to return to 0 */
  for (i = 0; i < 1000; i++)
    {
      oss_udelay (100);
      tmp = read_reg (devc, SPI, 0);
      if (!(tmp & 0x10000))
	{
	  valid = 1;
	  break;
	}
    }
  if (!valid)			/* Timed out */
    return 0;

  return 1;
}

static unsigned int
mix_scale (int left, int right, int bits)
{
  left = mix_cvt[left];
  right = mix_cvt[right];

  return ((left * ((1 << bits) - 1) / 100) << 8) | (right *
						    ((1 << bits) - 1) / 100);
}

static int
audigyls_set_volume (audigyls_devc * devc, int codecid, int value)
{
  audigyls_portc *portc = NULL;
  int left, right, i2s_vol;

  portc = &devc->portc[codecid];

  left = value & 0xff;
  right = (value >> 8) & 0xff;
  if (left > 100)
    left = 100;
  if (right > 100)
    right = 100;
  devc->playvol[codecid] = left | (right << 8);

  i2s_vol = 65535 - mix_scale (left, right, 8);
  write_reg (devc, MIXVOL_I2S, portc->play_port, (i2s_vol << 16));
  return devc->playvol[codecid];
}

int
audigyls_mix_control (int dev, int ctrl, unsigned int cmd, int value)
{
  audigyls_devc *devc = mixer_devs[dev]->hw_devc;
  int val;

  if (cmd == SNDCTL_MIX_READ)
    {
      value = 0;
      switch (ctrl)
	{
	case 1:		/* spread */
	  value = devc->spread;
	  break;

	case 2:		/* record what you hear */
	  value = devc->loopback;
	  break;

	case 3:
	  {
	    value = devc->recvol;
	  }
	  break;

	case 7:
	  value = devc->input_source;
	  break;
	}
    }
  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (ctrl)
	{
	case 1:		/* recording source */
	  devc->spread = value;
	  if (value)
	    write_reg (devc, HMIXMAP_I2S, 0, 0x10101010);
	  else
	    write_reg (devc, HMIXMAP_I2S, 0, 0x76543210);
	  break;

	case 2:		/* record what you hear */
	  devc->loopback = value;
	  break;

	case 3:
	  {
	    val = (255 - value) & 0xff;
	    write_reg (devc, P17RECVOLL, 0,
		       val << 24 | val << 16 | val << 8 | val);
	    write_reg (devc, P17RECVOLH, 0,
		       val << 24 | val << 16 | val << 8 | val);
	    write_reg (devc, SRCTL, 1, 0x30303030);
	    /* val << 24 | val << 16 | val << 8 | val); */
	    devc->recvol = value & 0xff;
	  }
	  break;

	case 4:
	  if (value == 0)	/* for mic input remove GPIO */
	    {
	      OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x18) | 0x400,
		    devc->base + 0x18);
	      audigyls_i2c_write (devc, 0x15, 0x2);	/* Mic */
	    }
	  else
	    {
	      OUTL (devc->osdev,
		    INL (devc->osdev, devc->base + 0x18) & ~0x400,
		    devc->base + 0x18);
	      audigyls_i2c_write (devc, 0x15, 0x4);	/* Line */
	    }
	  devc->input_source = value;
	  break;

	}
    }
  return value;
}

static int
audigyls_mix_init (int dev)
{
  int group, err;
  audigyls_devc *devc = mixer_devs[dev]->hw_devc;

  if ((group = mixer_ext_create_group (dev, 0, "EXT")) < 0)
    return group;

  if ((err = mixer_ext_create_control (dev, group, 1, audigyls_mix_control,
				       MIXT_ONOFF, "Spread", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 2, audigyls_mix_control,
				       MIXT_ONOFF, "LOOPBACK", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;


  if ((err = mixer_ext_create_control (dev, group, 3, audigyls_mix_control,
				       MIXT_MONOSLIDER, "RECORDVOL", 255,
				       MIXF_READABLE | MIXF_WRITEABLE |
				       MIXF_RECVOL)) < 0)
    return err;

  if (!devc->has_ac97)
    {
      if ((err =
	   mixer_ext_create_control (dev, group, 4, audigyls_mix_control,
				     MIXT_ENUM, "RECORDSRC", 2,
				     MIXF_READABLE | MIXF_WRITEABLE)) < 0)
	return err;
      mixer_ext_set_strings (dev, err, "MIC LINE", 0);
    }
  return 0;
}

static const int bindings[MAX_PORTC] = {
  DSP_BIND_FRONT,
  0,
  DSP_BIND_CENTER_LFE,
  DSP_BIND_SURR
};

static int
install_audio_devices (audigyls_devc * devc)
{
  int i;
  int frontdev = -1;
  int adev, flags;
  int fmts = AFMT_S16_LE | AFMT_AC3;
  static char *names[] = {
    "AudigyLS front",
    "AudigyLS (shadow)",
    "AudigyLS center/lfe",
    "AudigyLS surround"
  };

#if 0
  if (audigyls_spdif_enable == 1)
    n = 2;
#endif

  for (i = 0; i < MAX_PORTC; i++)
    {
      audigyls_portc *portc = &devc->portc[i];

      flags =
	ADEV_AUTOMODE | ADEV_16BITONLY | ADEV_STEREOONLY | ADEV_FIXEDRATE;

      switch (i)
	{
	case 0:
	  portc->play_port = 0;
	  portc->rec_port = 2;
	  flags |= ADEV_DUPLEX;
	  break;
	case 1:
	  portc->play_port = 0;
	  portc->rec_port = 2;
	  flags |= ADEV_DUPLEX | ADEV_SHADOW;
	  break;
	case 2:
	  portc->play_port = 1;
	  portc->rec_port = 2;
	  flags |= ADEV_NOINPUT;
	  break;
	case 3:
	  portc->play_port = 3;
	  portc->rec_port = 2;
	  flags |= ADEV_NOINPUT;
	  break;
	}

      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					names[i],
					&audigyls_audio_driver,
					sizeof (audiodrv_t),
					flags, fmts, NULL, -1)) < 0)
	{
	  return 0;
	}

      if (i == 0)
	frontdev = adev;

      audio_engines[adev]->portc = portc;
      audio_engines[adev]->devc = devc;
      audio_engines[adev]->max_fragments = 2;
      //audio_engines[adev]->dmabuf_alloc_flags |= DMABUF_SIZE_16BITS;
      audio_engines[adev]->rate_source = frontdev;
      audio_engines[adev]->mixer_dev = devc->mixer_dev;
      audio_engines[adev]->binding = bindings[i];
      if (audio_engines[adev]->flags & ADEV_FIXEDRATE)
	{
	  audio_engines[adev]->fixed_rate = DEFAULT_RATE;
	  audio_engines[adev]->min_rate = DEFAULT_RATE;
	  audio_engines[adev]->max_rate = DEFAULT_RATE;
	}
      else
	{
	  audio_engines[adev]->min_rate = 44100;
	  audio_engines[adev]->max_rate = 192000;
	}
      portc->audio_dev = adev;
      portc->open_mode = 0;
      devc->playvol[i] = 0x3030;
      devc->recvol = 128;
      portc->bits = AFMT_S16_LE;
    }

#ifdef USE_REMUX
  if (frontdev >= 0)
    {
      if (audigyls_spdif_enable && devc->has_ac97)
	remux_install ("AudigyLS 4.0 output", devc->osdev, frontdev,
		       frontdev + 3, -1, -1);
      else
	remux_install ("AudigyLS 5.1 output", devc->osdev, frontdev,
		       frontdev + 3, frontdev + 2, -1);
    }
#endif
  return 1;
}

static void
select_out3_mode (audigyls_devc * devc, int mode)
{
  /*
   * Set the out3/spdif combo jack format.
   * mode0=analog rear/center, 1=spdif
   */

  if (mode == 0)
    {
      write_reg (devc, SPC, 0, 0x00000f00);
    }
  else
    {
      write_reg (devc, SPC, 0, 0x0000000f);
    }
}

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

  if (((cmd >> 8) & 0xff) == 'M')
    {
      int val;

      if (IOC_IS_OUTPUT (cmd))
	switch (cmd & 0xff)
	  {
	  case SOUND_MIXER_RECSRC:
	    return *arg = 0;
	    break;

	  case SOUND_MIXER_PCM:
	    val = *arg;
	    return *arg = audigyls_set_volume (devc, 0, val);

	  case SOUND_MIXER_CENTERVOL:
	    val = *arg;
	    return *arg = audigyls_set_volume (devc, 2, val);

	  case SOUND_MIXER_REARVOL:
	    val = *arg;
	    return *arg = audigyls_set_volume (devc, 3, val);
	  }
      else
	switch (cmd & 0xff)	/* Return Parameter */
	  {
	  case SOUND_MIXER_RECSRC:
	  case SOUND_MIXER_RECMASK:
	    return *arg = 0;
	    break;

	  case SOUND_MIXER_DEVMASK:
	    return *arg =
	      SOUND_MASK_PCM | SOUND_MASK_REARVOL | SOUND_MASK_CENTERVOL;
	    break;

	  case SOUND_MIXER_STEREODEVS:
	    return *arg =
	      SOUND_MASK_PCM | SOUND_MASK_REARVOL | SOUND_MASK_CENTERVOL;
	    break;

	  case SOUND_MIXER_CAPS:
	    return *arg = SOUND_CAP_EXCL_INPUT;
	    break;

	  case SOUND_MIXER_PCM:
	    return *arg = devc->playvol[0];
	    break;

	  case SOUND_MIXER_CENTERVOL:
	    return *arg = devc->playvol[2];
	    break;

	  case SOUND_MIXER_REARVOL:
	    return *arg = devc->playvol[3];
	    break;
	  }
    }
  else
    return *arg = 0;

  return -EINVAL;
}

static mixer_driver_t audigyls_mixer_driver = {
  audigyls_mixer_ioctl
};

int
audigyls_attach (oss_device_t * osdev)
{
  int tmp, err, i, tries;
  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  unsigned int subvendor;
  audigyls_devc *devc;

  static unsigned int spi_dac[] = {
    0x00ff, 0x02ff, 0x0400, 0x530, 0x0622, 0x08ff, 0x0aff, 0x0cff,
    0x0eff, 0x10ff, 0x1200, 0x1400, 0x1800, 0x1aff, 0x1cff,
    0x1e00,
  };

  DDB (cmn_err (CE_WARN, "Entered AUDIGYLS probe routine\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 ||
      device != PCI_DEVICE_ID_CREATIVE_AUDIGYLS)
    return 0;

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


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

  devc->osdev = osdev;
  osdev->devc = devc;
  devc->card_name = "AudigyLS";
  devc->subvendor = subvendor;

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

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

  MUTEX_INIT (osdev, devc->mutex, MH_DRV);
  MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1);

  oss_register_device (osdev, devc->card_name);

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


/*
 * Init mixer
 */
  if (subvendor == 0x10021102)	/* original audigyls */
    {
      devc->mixer_dev = ac97_install (&devc->ac97devc, devc->card_name,
				      audigyls_ac97_read, audigyls_ac97_write,
				      devc, devc->osdev);
      devc->has_ac97 = 1;
      audigyls_ac97_write (devc, 0x1c, 0x8000);
    }
  else
    {
      devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
					   devc->osdev,
					   devc->osdev,
					   "AudigyLS Mixer",
					   &audigyls_mixer_driver,
					   sizeof (mixer_driver_t), devc);
      devc->has_ac97 = 0;	/* no ac97 */
      mixer_devs[devc->mixer_dev]->devc = devc;
      mixer_devs[devc->mixer_dev]->hw_devc = devc;
      mixer_devs[devc->mixer_dev]->priority = 1;	/* Possible default mixer candidate */
    }

  mixer_ext_set_init_fn (devc->mixer_dev, audigyls_mix_init, 10);

#if 0
  write_reg (devc, SCS0, 0, 0x02108504);
  write_reg (devc, SCS1, 0, 0x02108504);
  write_reg (devc, SCS2, 0, 0x02108504);
#endif
  write_reg (devc, SCS3, 0, 0x02108504);

  write_reg (devc, AUDCTL, 0, 0x0f0f003f);	/* enable all outputs */

  select_out3_mode (devc, audigyls_spdif_enable);

/**********************************************************************
 * In P17, there's 8 GPIO pins.
 * GPIO register: 0x00XXYYZZ
 * XX: Configure GPIO to be either GPI (0) or GPO (1).
 * YY: GPO values, applicable if the pin is configure to be GPO.
 * ZZ: GPI values, applicable if the pin is configure to be GPI.
 * 
 * in SB570, pin 0-4 and 6 is used as GPO and pin 5 and 7 is used as GPI.
 * 
 * GPO0:
 * 1 ==> Analog output
 * 0 ==> Digital output
 * GPO1:
 * 1 ==> Enable output on card
 * 0 ==> Diable output on card
 * GPO2:
 * 1 ==> Enable Mic Bias and Mic Path
 * 0 ==> Disable Mic Bias and Mic Path
 * GPO3:
 * 1 ==> Disable SPDIF-IO output
 * 0 ==> Enable SPDIF-IO output
 * GPO4 and GPO6:
 * DAC sampling rate selection:
 * Not applicable to SB570 since DAC is controlled through SPI
 * GPI5:
 * 1 ==> Front Panel is not connected
 * 0 ==> Front Panel is connected
 * GPI7:
 * 1 ==> Front Panel Headphone is not connected
 * 0 ==> Front Panel Headphone is connected
 ***********************************************************/

  OUTL (devc->osdev, 0, devc->base + 0x18);	/* GPIO */
  if (devc->has_ac97)
    OUTL (devc->osdev, 0x005f03a3, devc->base + 0x18);
  else
    {
      /* for SBLive 7.1 */
      OUTL (devc->osdev, 0x005f4301, devc->base + 0x18);
      audigyls_i2c_write (devc, 0x15, 0x4);
      tries = 0;
    again:
      for (i = 0; i < sizeof (spi_dac); i++)
	{
	  if (!audigyls_spi_write (devc, spi_dac[i]) && tries < 100)
	    {
	      tries++;
	      goto again;
	    }
	}
    }

  OUTL (devc->osdev, INTR_PCI | INTR_RXA | INTR_AI, devc->base + IE);
  OUTL (devc->osdev, 0x00000009, devc->base + 0x14);	/* Enable audio */

  tmp = read_reg (devc, SRCTL, 0);
  if (devc->has_ac97)
    tmp |= 0xf0c81000;		/* record src0/src1 from ac97 */
  else
    tmp |= 0x50c81000;		/* record src0/src1 from I2SIN */
  write_reg (devc, SRCTL, 0, tmp);
  write_reg (devc, HMIXMAP_I2S, 0, 0x76543210);	/* default out route */
  install_audio_devices (devc);

  if (devc->has_ac97)		/* only attach midi for AudigyLS */
    attach_mpu (devc);

  return 1;
}

int
audigyls_detach (oss_device_t * osdev)
{
  unsigned int status;
  audigyls_devc *devc = (audigyls_devc *) osdev->devc;

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


  write_reg (devc, SA, 0, 0);
  OUTL (devc->osdev, 0x00000000, devc->base + IE);	/* Interrupt disable */
  write_reg (devc, AINT_ENABLE, 0, 0);	/* Disable audio interrupts */
  status = INL (devc->osdev, devc->base + 0x08);
  OUTL (devc->osdev, status, devc->base + 0x08);	/* Acknowledge */
  oss_udelay (1000);
  if (devc->mpu_attached)
    unload_audigylsuart (devc);

  oss_unregister_interrupts (devc->osdev);

  MUTEX_CLEANUP (devc->mutex);
  MUTEX_CLEANUP (devc->low_mutex);

  oss_unregister_device (osdev);
  return 1;
}
