/*
 * Purpose: Driver for the Intel ICH AC97 audio controller
 *
 * The same design is also used in many PC chipsets by nVidia, AMD and SiS for
 * audio functionality.
 */

/*
 * 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 "ich_cfg.h"
#include <oss_pci.h>

#include <ac97.h>
extern int intelpci_force_mmio;

#define INTEL_VENDOR_ID         0x8086
#define SIS_VENDOR_ID           0x1039
#define NVIDIA_VENDOR_ID        0x10de
#define AMD_VENDOR_ID           0x1022
#define SIS_DEVICE_7012         0x7012
#define INTEL_DEVICE_ICH1       0x2415
#define INTEL_DEVICE_ICH1R1     0x2425
#define INTEL_DEVICE_ICH1R2     0x7195
#define INTEL_DEVICE_ICH2       0x2445
#define INTEL_DEVICE_ICH3       0x2485
#define INTEL_DEVICE_ICH4       0x24c5
#define INTEL_DEVICE_ICH5       0x24d5
#define INTEL_DEVICE_ESB        0x25a6
#define INTEL_DEVICE_ICH6       0x266e
#define INTEL_DEVICE_ICH7       0x27de
#define NVIDIA_DEVICE_NFORCE    0x01b1
#define NVIDIA_DEVICE_MCP4	0x003a
#define NVIDIA_DEVICE_NFORCE2   0x006a
#define	NVIDIA_DEVICE_CK8	0x008a
#define NVIDIA_DEVICE_NFORCE3   0x00da
#define NVIDIA_DEVICE_CK8S      0x00ea
#define	NVIDIA_DEVICE_NFORCE4	0x0059
#define	NVIDIA_DEVICE_MCP51	0x026b
#define AMD_DEVICE_768          0x7445
#define AMD_DEVICE_8111         0x746d

extern int intelpci_rate_tuning;

#define MAX_PORTC 3
#define BDL_SIZE	32

typedef struct
{
  int open_mode;
  int speed, bits, channels;
  int audio_enabled;
  int trigger_bits;
  int audiodev;
  int port_type;
#define DF_PCM 0
#define DF_SPDIF 1
}
ich_portc;

typedef struct
{
  unsigned int addr;
  unsigned short size;
  unsigned short flags;
}
bdl_t;

typedef struct ich_devc
{
  oss_device_t *osdev;
  oss_native_word base, ac97_base;
  oss_native_word membar_addr, ac97_membar_addr;
  char *membar_virt, *ac97_membar_virt;
#define CTL_BASE   0		/* addressing controller regs */
#define MIXER_BASE 1		/* addressing mixer regs */
  int mem_mode;
#define MMAP_MODE  0		/* ICH4/ICH5 uses MEM BARS */
#define IO_MODE    1		/* ICH1/2/3/4/5 uses IO BARS */

  int irq;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* Mixer */
  ac97_devc ac97devc;
  int mixer_dev;

  /* Audio parameters */
  int open_mode;
  int fifo_errors;

  /* Buffer Descriptor List */
  char *bdlBuffer;
  bdl_t *playBDL, *recBDL, *spdifBDL;
  oss_native_word playBDL_phys, recBDL_phys, spdifBDL_phys;

  int play_currbuf, play_currfrag;
  int spdif_currbuf, spdif_currfrag;
  int rec_currbuf, rec_currfrag;
#define INTEL_ICH1 0
#define INTEL_ICH3 1
#define INTEL_ICH4 2
#define SIS_7012   3
#define AMD_768    4
#define AMD_8111   5
#define NVIDIA_NFORCE  6
#define NVIDIA_NFORCE2 7
  int model;
  char *chip_name;
  ich_portc portc[MAX_PORTC];
  int play_frag_index[BDL_SIZE];
  int rec_frag_index[BDL_SIZE];
  int spdif_frag_index[BDL_SIZE];
}
ich_devc;

static unsigned int
ich_INL (ich_devc * devc, int base, unsigned int a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	return *(volatile unsigned int *) (devc->membar_virt + (a));
      else
	return *(volatile unsigned int *) (devc->ac97_membar_virt + (a));
    }
  else
    {
      if (base == CTL_BASE)
	return INL (devc->osdev, devc->base + a);
      else
	return INL (devc->osdev, devc->ac97_base + a);
    }
}

static unsigned short
ich_INW (ich_devc * devc, int base, unsigned short a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	return *(volatile unsigned short *) (devc->membar_virt + (a));
      else
	return *(volatile unsigned short *) (devc->ac97_membar_virt + (a));
    }
  else
    {
      if (base == CTL_BASE)
	return INW (devc->osdev, devc->base + a);
      else
	return INW (devc->osdev, devc->ac97_base + a);
    }
}

static unsigned char
ich_INB (ich_devc * devc, int base, unsigned char a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	return *(volatile unsigned char *) (devc->membar_virt + (a));
      else
	return *(volatile unsigned char *) (devc->ac97_membar_virt + (a));
    }
  else
    {
      if (base == CTL_BASE)
	return INB (devc->osdev, devc->base + a);
      else
	return INB (devc->osdev, devc->ac97_base + a);
    }
}

static void
ich_OUTL (ich_devc * devc, unsigned int d, int base, unsigned int a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	*(volatile unsigned int *) (devc->membar_virt + (a)) = d;
      else
	*(volatile unsigned int *) (devc->ac97_membar_virt + (a)) = d;
    }
  else
    {
      if (base == CTL_BASE)
	OUTL (devc->osdev, d, devc->base + a);
      else
	OUTL (devc->osdev, d, devc->ac97_base + a);
    }
}

static void
ich_OUTW (ich_devc * devc, unsigned short d, int base, unsigned short a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	*(volatile unsigned short *) (devc->membar_virt + (a)) = d;
      else
	*(volatile unsigned short *) (devc->ac97_membar_virt + (a)) = d;
    }
  else
    {
      if (base == CTL_BASE)
	OUTW (devc->osdev, d, devc->base + a);
      else
	OUTW (devc->osdev, d, devc->ac97_base + a);
    }
}

static void
ich_OUTB (ich_devc * devc, unsigned char d, int base, unsigned char a)
{
  if (devc->mem_mode == MMAP_MODE)
    {
      if (base == CTL_BASE)
	*(volatile unsigned char *) (devc->membar_virt + (a)) = d;
      else
	*(volatile unsigned char *) (devc->ac97_membar_virt + (a)) = d;
    }
  else
    {
      if (base == CTL_BASE)
	OUTB (devc->osdev, d, devc->base + a);
      else
	OUTB (devc->osdev, d, devc->ac97_base + a);
    }
}

static int
ac97_read (void *devc_, int reg)
{
  ich_devc *devc = devc_;
  int i = 100, status;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  status = ich_INB (devc, CTL_BASE, 0x34);

  while (status & 0x01 && i-- > 0)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      oss_udelay (10);
      MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
      status = ich_INB (devc, CTL_BASE, 0x34);
    }

  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ich_INW (devc, MIXER_BASE, reg);
}

static int
ac97_write (void *devc_, int reg, int data)
{
  ich_devc *devc = devc_;
  int i = 100, status;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  status = ich_INB (devc, CTL_BASE, 0x34);

  while (status & 0x01 && i-- > 0)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      oss_udelay (10);
      MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
      status = ich_INB (devc, CTL_BASE, 0x34);
    }

  ich_OUTW (devc, data, MIXER_BASE, reg);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 1;
}

/*
 * The top half interrupt handler
 */
static int
ichintr (oss_device_t * osdev)
{
  int serviced = 0;
  ich_devc *devc = osdev->devc;
  ich_portc *portc;
  unsigned int glob_status, status, p, f;
  oss_native_word flags;
  int i;


  flags = 0;			/* To prevent compiler warnings */
  MUTEX_ENTER (devc->mutex, flags);
  /* Get pending interrupts and acknowledge them */
  glob_status = ich_INL (devc, CTL_BASE, 0x30);
  ich_OUTL (devc, glob_status, CTL_BASE, 0x30);

  /*
   * Check the interrupt bits of the status register
   */
  if (!(glob_status & 0x0cf7))
    {
      /* Not for me */
      MUTEX_EXIT (devc->mutex, flags);
      return 0;
    }

  /*-------------------- Handle PCM -------------------*/
  if (devc->model == SIS_7012)
    status = ich_INB (devc, CTL_BASE, 0x18);
  else
    status = ich_INB (devc, CTL_BASE, 0x16);

  if (status & 0x10)		/* FIFO error */
    devc->fifo_errors++;

  if (status & 0x08)
    {
      for (i = 0; i < MAX_PORTC - 1; i++)
	{
	  portc = &devc->portc[i];
	  serviced = 1;
	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT))	/* IOC interrupt */
	    {
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
	      p = ich_INB (devc, CTL_BASE, 0x14);

	      if (p != devc->play_currbuf)
		{
		  p = devc->play_currbuf;
		  f = devc->play_currfrag;
		  devc->playBDL[p].addr =
		    dmap->dmabuf_phys + (f * dmap->fragment_size);

		  /* SIS uses bytes, intelpci uses samples */
		  if (devc->model == SIS_7012)
		    devc->playBDL[p].size = (dmap->fragment_size);
		  else
		    devc->playBDL[p].size = (dmap->fragment_size / 2);
		  devc->playBDL[p].flags = 0xc000;	/* IOC interrupts */

		  ich_OUTB (devc, p, CTL_BASE, 0x15);	/* Set LVD */
		  devc->play_frag_index[p] = f;
		  devc->play_currbuf = (p + 1) % BDL_SIZE;
		  devc->play_currfrag = (f + 1) % dmap->nfrags;
		}
	      oss_audio_outputintr (portc->audiodev, 1);
	    }
	}
      if (devc->model == SIS_7012)
	ich_OUTB (devc, status, CTL_BASE, 0x18);	/* Clear interrupts */
      else
	ich_OUTB (devc, status, CTL_BASE, 0x16);	/* Clear interrupts */
    }

  /*------------------- handle SPDIF interrupts -------------------------*/

  if (devc->model == NVIDIA_NFORCE2)
    {
      status = ich_INB (devc, CTL_BASE, 0x76);
      if (status & 0x08)
	{
	  portc = &devc->portc[2];
	  serviced = 1;
	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT))	/* IOC interrupt */
	    {
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
	      p = ich_INB (devc, CTL_BASE, 0x74);

	      if (p != devc->spdif_currbuf)
		{
		  p = devc->spdif_currbuf;
		  f = devc->spdif_currfrag;
		  devc->spdifBDL[p].addr =
		    dmap->dmabuf_phys + (f * dmap->fragment_size);
		  /* SIS uses bytes, intelpci uses samples */
		  devc->spdifBDL[p].size = (dmap->fragment_size / 2);
		  devc->spdifBDL[p].flags = 0xc000;	/* IOC interrupts */

		  ich_OUTB (devc, p, CTL_BASE, 0x75);	/* Set LVD */
		  devc->spdif_frag_index[p] = f;
		  devc->spdif_currbuf = (p + 1) % BDL_SIZE;
		  devc->spdif_currfrag = (f + 1) % dmap->nfrags;
		}
	      oss_audio_outputintr (portc->audiodev, 1);
	    }
	  ich_OUTB (devc, status, CTL_BASE, 0x76);	/* Clear interrupts */
	}
    }

  /*----------------------- Handle Recording Interrupts --------------------*/

  if (devc->model == SIS_7012)
    status = ich_INB (devc, CTL_BASE, 0x08);
  else
    status = ich_INB (devc, CTL_BASE, 0x06);

  if (status & 0x08)
    {
      for (i = 0; i < MAX_PORTC - 1; i++)
	{
	  portc = &devc->portc[i];
	  serviced = 1;
	  if ((portc->trigger_bits & PCM_ENABLE_INPUT))	/* IOC interrupt */
	    {
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in;
	      p = ich_INB (devc, CTL_BASE, 0x04);

	      if (p != devc->rec_currbuf)
		{
		  p = devc->rec_currbuf;
		  f = devc->rec_currfrag;
		  devc->recBDL[p].addr =
		    dmap->dmabuf_phys + (f * dmap->fragment_size);

		  /* SIS uses bytes, intelpci uses samples */
		  if (devc->model == SIS_7012)
		    devc->recBDL[p].size = (dmap->fragment_size);
		  else
		    devc->recBDL[p].size = (dmap->fragment_size / 2);

		  devc->recBDL[p].flags = 0xc000;	/* IOC interrupts */

		  ich_OUTB (devc, p, CTL_BASE, 0x05);	/* Set LVD */
		  devc->rec_frag_index[p] = f;
		  devc->rec_currbuf = (p + 1) % BDL_SIZE;
		  devc->rec_currfrag = (f + 1) % dmap->nfrags;
		}
	      oss_audio_inputintr (portc->audiodev, 0);
	    }
	}
      if (devc->model == SIS_7012)
	ich_OUTB (devc, status, CTL_BASE, 0x08);	/* Clear int */
      else
	ich_OUTB (devc, status, CTL_BASE, 0x06);	/* Clear int */
    }
  MUTEX_EXIT (devc->mutex, flags);
  return serviced;
}


/*
 * Audio routines
 */

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

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

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

  if (arg > 48000)
    arg = 48000;
  if (arg < 5000)
    arg = 5000;
  portc->speed = arg;
  return portc->speed;
}

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

  if ((arg == 1) || (arg == 2))
    {
      audio_engines[dev]->flags |= ADEV_STEREOONLY;
      arg = 2;
    }
  else
    audio_engines[dev]->flags &= ~ADEV_STEREOONLY;

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

  return portc->channels;
}

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

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

#if 1
  if (portc->open_mode & OPEN_READ)
    return portc->bits = AFMT_S16_LE;
#endif
  if (!(arg & (AFMT_S16_LE | AFMT_AC3)))
    return portc->bits;
  portc->bits = arg;

  return portc->bits;
}

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

static void ich_audio_trigger (int dev, int state);

static void
ich_audio_reset (int dev)
{
  ich_audio_trigger (dev, 0);
}

static void
ich_audio_reset_input (int dev)
{
  ich_portc *portc = audio_engines[dev]->portc;
  ich_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
ich_audio_reset_output (int dev)
{
  ich_portc *portc = audio_engines[dev]->portc;
  ich_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
ich_audio_open (int dev, int mode, int openflags)
{
  ich_portc *portc = audio_engines[dev]->portc;
  ich_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 (portc->port_type == DF_SPDIF)
    {
      if (mode & OPEN_READ)
	{
	  cmn_err (CE_NOTE, "The S/PDIF device supports only playback\n");
	  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	  return -EIO;
	}
    }
  else
    {
      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;
  devc->fifo_errors = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
ich_audio_close (int dev, int mode)
{
  ich_portc *portc = audio_engines[dev]->portc;
  ich_devc *devc = audio_engines[dev]->devc;

  ich_audio_reset (dev);
  portc->open_mode = 0;

  if (devc->fifo_errors > 0)
    cmn_err (CE_CONT, "%d fifo errors were detected\n", devc->fifo_errors);

  if (portc->port_type != DF_SPDIF)
    devc->open_mode &= ~mode;
  portc->audio_enabled &= ~mode;
}

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

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

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

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

static void
ich_audio_trigger (int dev, int state)
{
  ich_devc *devc = audio_engines[dev]->devc;
  ich_portc *portc = audio_engines[dev]->portc;
  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))
	    {
	      if (portc->port_type == DF_SPDIF)
		ich_OUTB (devc, 0x1d, CTL_BASE, 0x7b);	/* Kickstart */
	      else
		ich_OUTB (devc, 0x1d, CTL_BASE, 0x1b);	/* Kickstart */
	      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;
	      if (portc->port_type == DF_SPDIF)
		ich_OUTB (devc, 0x00, CTL_BASE, 0x7b);	/* reset */
	      else
		ich_OUTB (devc, 0x00, CTL_BASE, 0x1b);	/* reset */
	    }
	}
    }
  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      ich_OUTB (devc, 0x1d, CTL_BASE, 0x0b);	/* Kickstart */
	      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;
	      ich_OUTB (devc, 0x00, CTL_BASE, 0x0b);	/* reset */
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
ich_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  ich_devc *devc = audio_engines[dev]->devc;
  ich_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  int i, n, speed;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  ich_OUTB (devc, 0x02, CTL_BASE, 0x0b);	/* Reset */
  ich_OUTL (devc, devc->recBDL_phys, CTL_BASE, 0x00);	/* BDL base */

  speed = portc->speed;
  speed = (speed * 240) / intelpci_rate_tuning;
  ac97_recrate (&devc->ac97devc, speed);

  n = bcount;
  if (n > BDL_SIZE)
    n = BDL_SIZE;

  for (i = 0; i < n; i++)
    {
      devc->recBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);

      /* SiS7012 uses bytes, ICH uses samples */
      if (devc->model == SIS_7012)
	devc->recBDL[i].size = (dmap->fragment_size);
      else
	devc->recBDL[i].size = (dmap->fragment_size / 2);

      devc->recBDL[i].flags = 0xc000;	/* IOC interrupts */
      devc->rec_frag_index[i] = i;
    }
  ich_OUTB (devc, n - 1, CTL_BASE, 0x05);	/* Set last valid descriptor */

  devc->rec_currbuf = n % BDL_SIZE;
  devc->rec_currfrag = n;
  if (devc->rec_currfrag >= dmap->nfrags)
    devc->rec_currfrag = 0;

  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

/*ARGSUSED*/
static int
ich_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  ich_devc *devc = audio_engines[dev]->devc;
  ich_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  int i, n, speed;
  unsigned int tmp;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  /* We need to add ac3 pass through support */
  if (devc->model == SIS_7012)
    {
      if (portc->bits == AFMT_AC3)
	ich_OUTB (devc, 0, CTL_BASE, 0x4c);
      else
	ich_OUTB (devc, 1, CTL_BASE, 0x4c);
    }

  ac97_spdif_setup (devc->mixer_dev, portc->speed, portc->bits);

  if (portc->bits == AFMT_AC3)
    {
      portc->channels = 2;
      portc->bits = 16;
    }

  /* do SPDIF out */
  if ((portc->port_type == DF_SPDIF) && (devc->model == NVIDIA_NFORCE2))
    {
      ich_OUTB (devc, 0x02, CTL_BASE, 0x7b);	/* Reset */
      ich_OUTL (devc, devc->spdifBDL_phys, CTL_BASE, 0x70);	/* BDL base */
      n = bcount;
      if (n > BDL_SIZE)
	n = BDL_SIZE;

      for (i = 0; i < n; i++)
	{
	  devc->spdifBDL[i].addr =
	    dmap->dmabuf_phys + (i * dmap->fragment_size);
	  devc->spdifBDL[i].size = (dmap->fragment_size / 2);
	  devc->spdifBDL[i].flags = 0xc000;	/* IOC interrupts */
	  devc->spdif_frag_index[i] = i;
	}
      ich_OUTB (devc, n - 1, CTL_BASE, 0x75);	/* Set last valid
						 * descriptor */
      devc->spdif_currbuf = n % BDL_SIZE;
      devc->spdif_currfrag = n;
      if (devc->spdif_currfrag >= dmap->nfrags)
	devc->spdif_currfrag = 0;

      portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;

      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

      return 0;
    }
  /* else do PCM */
  ich_OUTB (devc, 0x02, CTL_BASE, 0x1b);	/* Reset */
  ich_OUTL (devc, devc->playBDL_phys, CTL_BASE, 0x10);	/* BDL base */

  speed = portc->speed;
  speed = (speed * 240) / intelpci_rate_tuning;
  ac97_playrate (&devc->ac97devc, speed);

  /* Handle 4/6 channel output on 7012 */
  if (devc->model == SIS_7012)
    {
      tmp = ich_INL (devc, CTL_BASE, 0x30);

      /* set default to 2 channel mode */
      ich_OUTB (devc, ich_INB (devc, CTL_BASE, 0x2c) & 0x3f, CTL_BASE, 0x2c);

      if ((portc->channels == 4) && (tmp & (1 << 20)))
	ich_OUTB (devc, (ich_INB (devc, CTL_BASE, 0x2c) & 0x3f) | 0x40,
		  CTL_BASE, 0x2c);

      if ((portc->channels == 6) && (tmp & (1 << 21)))
	ich_OUTB (devc, (ich_INB (devc, CTL_BASE, 0x2c) & 0x3f) | 0x80,
		  CTL_BASE, 0x2c);
    }
  /* Handle 4/6 channel output on evrything other than ICH1 and SIS7012 */
  if ((devc->model != INTEL_ICH1) && (devc->model != SIS_7012))
    {
      tmp = ich_INL (devc, CTL_BASE, 0x30);

      /* set default to 2 channel mode */
      ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x2c) & 0x0cfffff, CTL_BASE,
		0x2c);

      if ((portc->channels == 4) && (tmp & (1 << 20)))
	ich_OUTL (devc,
		  (ich_INL (devc, CTL_BASE, 0x2c) & 0x00fffff) | 0x0100000,
		  CTL_BASE, 0x2c);

      if ((portc->channels == 6) && (tmp & (1 << 21)))
	ich_OUTL (devc,
		  (ich_INL (devc, CTL_BASE, 0x2c) & 0x00fffff) | 0x0200000,
		  CTL_BASE, 0x2c);
    }
  n = bcount;
  if (n > BDL_SIZE)
    n = BDL_SIZE;

  for (i = 0; i < n; i++)
    {
      devc->playBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);

      /* SiS7012 uses bytes, ICH uses samples */
      if (devc->model == SIS_7012)
	devc->playBDL[i].size = (dmap->fragment_size);
      else
	devc->playBDL[i].size = (dmap->fragment_size / 2);

      devc->playBDL[i].flags = 0xc000;	/* IOC interrupts */
      devc->play_frag_index[i] = i;
    }
  ich_OUTB (devc, n - 1, CTL_BASE, 0x15);	/* Set last valid descriptor */


  devc->play_currbuf = n % BDL_SIZE;
  devc->play_currfrag = n;
  if (devc->play_currfrag >= dmap->nfrags)
    devc->play_currfrag = 0;

  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static int
ich_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  ich_devc *devc = audio_engines[dev]->devc;
  ich_portc *portc = audio_engines[dev]->portc;
  int p = 0, f = 0, c = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_OUTPUT)
    {
      if (portc->port_type == DF_PCM)
	{
	  f = ich_INB (devc, CTL_BASE, 0x14);	/* Current buffer */

	  if (devc->model == SIS_7012)
	    p = ich_INW (devc, CTL_BASE, 0x16);	/* Position in current
						 * buffer */
	  else
	    p = ich_INW (devc, CTL_BASE, 0x18);	/* Position in current
						 * buffer */
	  c = devc->play_frag_index[f];	/* Current fragment */

	  if (devc->model == SIS_7012)
	    p = dmap->fragment_size - (p);	/* Remaining bytes */
	  else
	    p = dmap->fragment_size - (p * 2);	/* Remaining bytes */
	}
      if ((portc->port_type == DF_SPDIF) && (devc->model == NVIDIA_NFORCE2))
	{
	  f = ich_INB (devc, CTL_BASE, 0x74);	/* Current buffer */
	  p = ich_INW (devc, CTL_BASE, 0x78);	/* Position in current
						 * buffer */
	  c = devc->play_frag_index[f];	/* Current fragment */
	  p = dmap->fragment_size - (p * 2);	/* Remaining bytes */
	}
    }
  /*
   * Handle input
   */
  if (direction == PCM_ENABLE_INPUT)
    {

      f = ich_INB (devc, CTL_BASE, 0x04);	/* Current buffer */

      if (devc->model == SIS_7012)
	p = ich_INW (devc, CTL_BASE, 0x06);	/* Position in current
						 * buffer */
      else
	p = ich_INW (devc, CTL_BASE, 0x08);	/* Position in current
						 * buffer */
      c = devc->rec_frag_index[f];	/* Current fragment */

      if (devc->model == SIS_7012)
	p = dmap->fragment_size - (p);	/* Remaining bytes */
      else
	p = dmap->fragment_size - (p * 2);	/* Remaining bytes */
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return p + c * dmap->fragment_size;
}

#if 0
static int
ich_calibrate_speed (int dev, int nominal_speed, int true_speed)
{
  unsigned int fix;
  DDB (smn_err (CE_CONT,
		"ich_calibrate_speed(%d, %d, %d)\n", dev, nominal_speed,
		true_speed));

  fix = ((240 * true_speed) + nominal_speed / 2) / nominal_speed;
  DDB (cmn_err (CE_NOTE, "intelpci_rate_tuning = %d\n", fix));
  if (fix > 1)
    intelpci_rate_tuning = fix;

  return 0;
}
#endif

static const audiodrv_t ich_audio_driver = {
  ich_audio_open,
  ich_audio_close,
  ich_audio_output_block,
  ich_audio_start_input,
  ich_audio_ioctl,
  ich_audio_prepare_for_input,
  ich_audio_prepare_for_output,
  ich_audio_reset,
  NULL,
  NULL,
  ich_audio_reset_input,
  ich_audio_reset_output,
  ich_audio_trigger,
  ich_audio_set_rate,
  ich_audio_set_format,
  ich_audio_set_channels,
  NULL,
  NULL,
  NULL,				/* ich_check_input, */
  NULL,				/* ich_check_output, */
  NULL,				/* ich_alloc_buffer, */
  NULL,				/* ich_free_buffer, */
  NULL,
  NULL,
  ich_get_buffer_pointer,
  NULL				/* ich_calibrate_speed */
};

static int
ich_init (ich_devc * devc)
{
  int my_mixer, adev, opts;
  int i, max_port;
  unsigned int reg;
  int first_dev = 0;
  oss_native_word phaddr;

  /* ACLink on, warm reset */
  reg = ich_INL (devc, CTL_BASE, 0x2c);
  if ((reg & 0x02) == 0)
    reg |= 2;
  else
    reg |= 4;
  reg &= ~8;
  ich_OUTL (devc, reg, CTL_BASE, 0x2c);
  oss_udelay (500);
  if (devc->model == SIS_7012)
    {
      reg |= 0x10;
      ich_OUTL (devc, reg, CTL_BASE, 0x2c);
      oss_udelay (500);
    }
  /* disable interrupts */
  ich_OUTB (devc, 0x00, CTL_BASE, 0x0b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x1b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x2b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x7b);

  devc->bdlBuffer =
    CONTIG_MALLOC (devc->osdev, 4 * 32 * 32, MEMLIMIT_32BITS, &phaddr);
  if (devc->bdlBuffer == NULL)
    {
      cmn_err (CE_WARN, "Failed to allocate BDL\n");
      return 0;
    }
  devc->playBDL = (bdl_t *) devc->bdlBuffer;
  devc->playBDL_phys = phaddr;
  devc->recBDL = (bdl_t *) (devc->bdlBuffer + (32 * 32));
  devc->recBDL_phys = phaddr + 32 * 32;
  devc->spdifBDL = (bdl_t *) (devc->bdlBuffer + (2 * 32 * 32));
  devc->spdifBDL_phys = phaddr + 2 * 32 * 32;

  /*
   * Init mixer
   */
  my_mixer =
    ac97_install (&devc->ac97devc, "ICH AC97 Mixer", ac97_read, ac97_write,
		  devc, devc->osdev);
  if (my_mixer == -1)
    {
      cmn_err (CE_WARN, "AC97 mixer installation failed\n");
      return 0;			/* No mixer */
    }

  devc->mixer_dev = my_mixer;
  mixer_devs[my_mixer]->priority = 10;	/* Known motherboard device */

  /* enable S/PDIF */
  devc->ac97devc.spdif_slot = SPDIF_SLOT34;
  ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 1);

  /* enable variable rate mode */
  ac97_write (devc, 0x2a, ac97_read (devc, 0x2a) | 9);
  if (!(ac97_read (devc, 0x2a) & 1))
    DDB (cmn_err (CE_NOTE, "VRA not supported...using GRC\n"));

  /* Enable SPDIF for SiS 7012 */
  if (devc->model == SIS_7012)
    {
      ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x2c) | (1 << 10), CTL_BASE,
		0x2c);
      ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x4c) | 1, CTL_BASE, 0x4c);
    }
  if (devc->model == NVIDIA_NFORCE2)
    max_port = 3;
  else
    max_port = 2;

  for (i = 0; i < max_port; i++)
    {
      ich_portc *portc = &devc->portc[i];
      char tmp_name[100];
      int port_fmt = DF_PCM;
      int formats = AFMT_S16_LE | AFMT_AC3;
      strcpy (tmp_name, devc->chip_name);
      opts = ADEV_AUTOMODE | ADEV_16BITONLY | ADEV_STEREOONLY | ADEV_COLD;

      if (!ac97_varrate (&devc->ac97devc))
	{
	  opts |= ADEV_FIXEDRATE;
	}
      if (i == 0)
	{
	  strcpy (tmp_name, devc->chip_name);
	  opts |= ADEV_DUPLEX;
	}
      if (i == 1)
	{
	  strcpy (tmp_name, devc->chip_name);
	  opts |= ADEV_DUPLEX | ADEV_SHADOW;
	}
      if (i == 2)
	{
	  sprintf (tmp_name, "%s S/PDIF out", devc->chip_name);
	  opts |= ADEV_NOINPUT | ADEV_SPECIAL | ADEV_FIXEDRATE;
	  port_fmt = DF_SPDIF;
	  oss_audio_set_devname ("spdout");
	}
      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&ich_audio_driver,
					sizeof (audiodrv_t),
					opts, formats, NULL, -1)) < 0)
	{
	  adev = -1;
	  return 0;
	}
      else
	{
	  if (i == 0)
	    first_dev = adev;
	  audio_engines[adev]->devc = devc;
	  audio_engines[adev]->portc = portc;
	  audio_engines[adev]->rate_source = first_dev;
	  audio_engines[adev]->mixer_dev = my_mixer;
	  audio_engines[adev]->max_block = 64 * 1024;

	  /* fix a timeout bug with Nforce2 */
	  if ((devc->model == NVIDIA_NFORCE) ||
	      (devc->model == NVIDIA_NFORCE2))
	    {
	      audio_engines[adev]->min_block = 4096;
	      audio_engines[adev]->max_block = 4096;
	    }

	  audio_engines[adev]->min_rate =
	    (opts & ADEV_FIXEDRATE) ? 48000 : 5000;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  audio_engines[adev]->min_channels = 2;
	  audio_engines[adev]->max_channels = 6;
	  portc->open_mode = 0;
	  portc->audio_enabled = 0;
	  portc->audiodev = adev;
	  portc->port_type = port_fmt;
	  if (audio_engines[adev]->flags & ADEV_FIXEDRATE)
	    audio_engines[adev]->fixed_rate = 48000;
	}
    }
  return 1;
}

int
ich_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision /* , pci_latency */ ;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr0, pci_ioaddr1;
  unsigned int dw;

  ich_devc *devc;

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

  devc->osdev = osdev;
  osdev->devc = devc;
  devc->open_mode = 0;

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

  DDB (cmn_err
       (CE_CONT, "ich_attach(Vendor %x, device %x)\n", vendor, device));

  if ((vendor != INTEL_VENDOR_ID && vendor != SIS_VENDOR_ID &&
       vendor != NVIDIA_VENDOR_ID && vendor != AMD_VENDOR_ID) ||
      (device != INTEL_DEVICE_ICH1 && device != INTEL_DEVICE_ICH1R1 &&
       device != INTEL_DEVICE_ICH1R2 && device != INTEL_DEVICE_ICH2 &&
       device != INTEL_DEVICE_ICH3 && device != INTEL_DEVICE_ICH4 &&
       device != INTEL_DEVICE_ICH5 && device != INTEL_DEVICE_ESB &&
       device != INTEL_DEVICE_ICH6 && device != INTEL_DEVICE_ICH7 &&
       device != SIS_DEVICE_7012 &&
       device != AMD_DEVICE_768 && device != AMD_DEVICE_8111 &&
       device != NVIDIA_DEVICE_NFORCE && device != NVIDIA_DEVICE_NFORCE2 &&
       device != NVIDIA_DEVICE_NFORCE3 && device != NVIDIA_DEVICE_CK8S &&
       device != NVIDIA_DEVICE_NFORCE4 && device != NVIDIA_DEVICE_CK8 &&
       device != NVIDIA_DEVICE_MCP51) && device != NVIDIA_DEVICE_MCP4)
    {
      cmn_err (CE_WARN, "Hardware not recognized (vendor=%x, dev=%x)\n",
	       vendor, device);
      return 0;
    }

  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_ioaddr0);
  pci_read_config_dword (osdev, PCI_BASE_ADDRESS_1, &pci_ioaddr1);

  switch (device)
    {
    case INTEL_DEVICE_ICH1:
      devc->model = INTEL_ICH1;
      devc->chip_name = "Intel ICH (2415)";
      break;
    case INTEL_DEVICE_ICH1R1:
      devc->model = INTEL_ICH1;
      devc->chip_name = "Intel ICHR1(2425)";
      break;
    case INTEL_DEVICE_ICH1R2:
      devc->model = INTEL_ICH1;
      devc->chip_name = "Intel ICHR2 (7195)";
      break;
    case INTEL_DEVICE_ICH2:
      devc->model = INTEL_ICH1;
      devc->chip_name = "Intel ICH2 (2445)";
      break;
    case INTEL_DEVICE_ICH3:
      devc->model = INTEL_ICH3;
      devc->chip_name = "Intel ICH3 (2485)";
      break;
    case INTEL_DEVICE_ICH4:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH4 (24C5)";
      break;
    case INTEL_DEVICE_ICH5:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH5 (24D5)";
      break;
    case INTEL_DEVICE_ICH6:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH6 (266E)";
      break;
    case INTEL_DEVICE_ICH7:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH7 (27DE)";
      break;
    case INTEL_DEVICE_ESB:
      devc->model = INTEL_ICH4;
      devc->chip_name = "Intel ICH5 (25a6)";
      break;
    case SIS_DEVICE_7012:
      devc->model = SIS_7012;
      devc->chip_name = "SiS 7012";
      break;
    case NVIDIA_DEVICE_NFORCE:
      devc->model = NVIDIA_NFORCE;
      devc->chip_name = "Nvidia nForce";
      break;
    case NVIDIA_DEVICE_NFORCE2:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia nForce2";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_NFORCE3:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia nForce3";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_CK8S:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia CK8S";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_NFORCE4:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia nForce4";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_CK8:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia CK8";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_MCP51:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia MCP51";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case NVIDIA_DEVICE_MCP4:
      devc->model = NVIDIA_NFORCE2;
      devc->chip_name = "Nvidia MCP4";
      pci_read_config_dword (osdev, 0x4c, &dw);
      dw |= 0x1000000;
      pci_write_config_dword (osdev, 0x4c, dw);
      break;
    case AMD_DEVICE_768:
      devc->model = AMD_768;
      devc->chip_name = "AMD 768";
      break;
    case AMD_DEVICE_8111:
      devc->model = AMD_8111;
      devc->chip_name = "AMD 8111";
      break;
    default:
      devc->chip_name = "Unknown ICH chip";
    }

  if (((pci_ioaddr1 == 0) || (intelpci_force_mmio)) &&
      (devc->model == INTEL_ICH4))
    {
      unsigned int ioaddr;

      /* read bar2 and bar3 for getting mmap address */
      pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_2, &ioaddr);
      devc->ac97_membar_addr = ioaddr;
      pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_3, &ioaddr);
      devc->membar_addr = ioaddr;

      /* get virtual address */
      devc->ac97_membar_virt =
	(char *) MAP_PCI_MEM (devc->osdev, 2, devc->ac97_membar_addr, 512);
      devc->membar_virt =
	(char *) MAP_PCI_MEM (devc->osdev, 3, devc->membar_addr, 256);
      devc->mem_mode = MMAP_MODE;
    }
  else
    devc->mem_mode = IO_MODE;

  if (devc->mem_mode == IO_MODE)
    {
      if (devc->model == INTEL_ICH4)
	{
	  /*
	   * enable the IOSE bit in 0x41 for legacy
	   * mode for ICH4/ICH5
	   */
	  pci_write_config_byte (osdev, 0x41, 1);

	  /* Set the secondary codec ID */
	  pci_write_config_byte (osdev, 0x40, 0x39);
	}
      /* Remove I/O space marker in bit 0. */
      devc->ac97_base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr0);
      devc->base = MAP_PCI_IOADDR (devc->osdev, 1, pci_ioaddr1);
      devc->ac97_base &= ~0xF;
      devc->base &= ~0xF;
    }
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  devc->irq = pci_irq_line;

  if (devc->mem_mode != IO_MODE)
    {
      devc->base = devc->membar_addr;
    }

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

  oss_register_device (osdev, devc->chip_name);

  if (oss_register_interrupts (devc->osdev, 0, ichintr, NULL) < 0)
    {
      cmn_err (CE_WARN, "Unable to install interrupt handler\n");
      return 0;
    }

  return ich_init (devc);	/* Detected */
}

int
ich_detach (oss_device_t * osdev)
{
  ich_devc *devc = (ich_devc *) osdev->devc;

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

  /* disable interrupts */
  ich_OUTB (devc, 0x00, CTL_BASE, 0x0b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x1b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x2b);
  ich_OUTB (devc, 0x00, CTL_BASE, 0x7b);
  oss_unregister_interrupts (devc->osdev);

  /* disable S/PDIF */
  if (devc->mixer_dev)
    ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 0);

  if (devc->bdlBuffer)
    CONTIG_FREE (devc->osdev, devc->bdlBuffer, 4 * 32 * 32);
  devc->bdlBuffer = NULL;

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

  if ((devc->mem_mode == MMAP_MODE) && (devc->membar_addr != 0))
    {
      UNMAP_PCI_MEM (devc->osdev, 2, devc->ac97_membar_addr,
		     devc->ac97_membar_virt, 512);
      UNMAP_PCI_MEM (devc->osdev, 3, devc->membar_addr, devc->membar_virt,
		     256);
      devc->membar_addr = 0;
      devc->ac97_membar_addr = 0;
    }
  oss_unregister_device (osdev);
  return 1;
}
