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

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

#include "via97_cfg.h"
#include "oss_pci.h"
#include "ac97.h"

#define VIA_VENDOR_ID		0x1106
#define VIA_82C686			0x3058

#define CODEC_TIMEOUT_COUNT     500
#define AC97CODEC       0x80	/*Access AC97 Codec */
#define IN_CMD        0x01000000	/*busy in sending */
#define STA_VALID     0x02000000	/*1:status data is valid */
#define CODEC_RD      0x00800000	/*Read CODEC status */
#define CODEC_INDEX   0x007F0000	/*Index of command register to access */
#define CODEC_DATA    0x0000FFFF	/*AC97 status register data */

typedef struct
{
  unsigned int phaddr;
  unsigned int flags;
}
SGD_entry;

typedef struct
{
  int open_mode;
  int speed, bits, channels;
  int audiodev;
  int audio_enabled;
  int trigger_bits;
}
via97_portc;

typedef struct via97_devc
{
  oss_device_t *osdev;
  oss_native_word base;
  int irq;
  int open_mode;
  char *chip_name;
/*
 * Mixer
 */
  ac97_devc ac97devc;

/*
 *  MIDI
 */

  int mpu_base;
  int mpu_attached;

  /* Audio parameters */
  via97_portc portc[2];
  oss_mutex_t mutex;		/* For normal locking */
  oss_mutex_t low_mutex;	/* For low level routines */

  /* Memory allocation and scatter/gather info */
#define BUFFER_SIZE		(128*1024)

/* NOTE! Check SGD_ALLOC if changing SGD_SIZE */
#define MIN_BLOCK		64
#define SGD_SIZE		(BUFFER_SIZE/MIN_BLOCK)
#define SGD_TOTAL_SIZE		(2*SGD_SIZE)
#define SGD_ALLOC		(SGD_TOTAL_SIZE*8)

  SGD_entry *SGD_table;
  oss_native_word SGD_table_phys;
  int play_sgd_ptr, rec_sgd_ptr;
  unsigned int play_sgd_phys, rec_sgd_phys;
}
via97_devc;

static int
ac97_read (void *devc_, int wIndex)
{
  oss_native_word dwWriteValue = 0, dwTmpValue;
  unsigned int i = 0;
  oss_native_word flags;
  via97_devc *devc = devc_;

  /* Index has only 7 bit */
  if (wIndex > 0x7F)
    return 0;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  dwWriteValue = ((oss_native_word) wIndex << 16) + CODEC_RD;
  OUTL (devc->osdev, dwWriteValue, devc->base + AC97CODEC);
  oss_udelay (100);
  /* Check AC CODEC access time out */
  for (i = 0; i < CODEC_TIMEOUT_COUNT; i++)
    {
      /* if send command over, break */
      if (INL (devc->osdev, devc->base + AC97CODEC) & STA_VALID)
	break;
      oss_udelay (50);
    }
  if (i == CODEC_TIMEOUT_COUNT)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }

  /* Check if Index still ours? If yes, return data, else return FAIL */
  dwTmpValue = INL (devc->osdev, devc->base + AC97CODEC);
  OUTB (devc->osdev, 0x02, devc->base + AC97CODEC + 3);
  if (((dwTmpValue & CODEC_INDEX) >> 16) == wIndex)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return ((int) dwTmpValue & CODEC_DATA);
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;

}

static int
ac97_write (void *devc_, int wIndex, int wData)
{
  oss_native_word dwWriteValue = 0;
  unsigned int i = 0;
  oss_native_word flags;
  via97_devc *devc = devc_;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  dwWriteValue = ((oss_native_word) wIndex << 16) + wData;
  OUTL (devc->osdev, dwWriteValue, devc->base + AC97CODEC);
  oss_udelay (100);

  /* Check AC CODEC access time out */
  for (i = 0; i < CODEC_TIMEOUT_COUNT; i++)
    {
      /* if send command over, break */
      if (!(INL (devc->osdev, devc->base + AC97CODEC) & IN_CMD))
	break;
      oss_udelay (50);
    }
  if (i == CODEC_TIMEOUT_COUNT)
    {
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 1;
}

static int
via97intr (oss_device_t * osdev)
{
  via97_devc *devc = (via97_devc *) osdev->devc;
  via97_portc *portc;
  unsigned char status;
  int serviced = 0;
  int i;

#ifdef OBSOLETED_STUFF
  if (devc->mpu_attached)
    {
      if (uart401intr (INT_HANDLER_CALL (devc->irq)))
	serviced = 1;
    }
#endif

  /* Handle playback interrupt */
  status = INB (devc->osdev, devc->base + 0x00);

  if (status & 0x01)
    {
      serviced = 1;
      for (i = 0; i < 2; i++)
	{
	  portc = &devc->portc[i];
	  /* IOC Interrupt */
	  if ((portc->trigger_bits & PCM_ENABLE_OUTPUT) && (status & 0x01))
	    oss_audio_outputintr (portc->audiodev, 0);
	}
    }
  OUTB (devc->osdev, status | 0x01, devc->base + 0x00);


  /* Handle record interrupt */
  status = INB (devc->osdev, devc->base + 0x10);

  if (status & 0x01)
    {
      serviced = 1;
      for (i = 0; i < 2; i++)
	{
	  portc = &devc->portc[i];
	  /* IOC Interrupt */
	  if ((portc->trigger_bits & PCM_ENABLE_INPUT) && (status & 0x01))
	    oss_audio_inputintr (portc->audiodev, 0);
	}
    }

  OUTB (devc->osdev, status | 0x01, devc->base + 0x10);
  return serviced;
}

/*
 * Audio routines
 */

static int
via97_audio_set_rate (int dev, int arg)
{
  via97_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
via97_audio_set_channels (int dev, short arg)
{
  via97_portc *portc = audio_engines[dev]->portc;

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


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

  return portc->channels;
}

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

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

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

  return portc->bits;
}

static int
via97_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static void via97_audio_trigger (int dev, int state);

static void
via97_audio_reset (int dev)
{
  via97_audio_trigger (dev, 0);
}

static void
via97_audio_reset_input (int dev)
{
  via97_portc *portc = audio_engines[dev]->portc;
  via97_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
via97_audio_reset_output (int dev)
{
  via97_portc *portc = audio_engines[dev]->portc;
  via97_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

static int
via97_audio_open (int dev, int mode, int open_flags)
{
  via97_portc *portc = audio_engines[dev]->portc;
  via97_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;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
via97_audio_close (int dev, int mode)
{
  via97_portc *portc = audio_engines[dev]->portc;
  via97_devc *devc = audio_engines[dev]->devc;

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

static void
via97_audio_output_block (int dev, oss_native_word buf, int count,
			  int fragsize, int intrflag)
{
  via97_portc *portc = audio_engines[dev]->portc;

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

}

static void
via97_audio_start_input (int dev, oss_native_word buf, int count,
			 int fragsize, int intrflag)
{
  via97_portc *portc = audio_engines[dev]->portc;

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

}

static void
via97_audio_trigger (int dev, int state)
{
  via97_devc *devc = audio_engines[dev]->devc;
  via97_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))
	    {
	      OUTB (devc->osdev, 0x80, devc->base + 0x01);
	      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;
	      OUTB (devc->osdev, 0x40, devc->base + 0x01);
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {

	      OUTB (devc->osdev, 0x80, devc->base + 0x11);
	      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;
	      OUTB (devc->osdev, 0x40, devc->base + 0x11);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static int
via97_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  via97_devc *devc = audio_engines[dev]->devc;
  via97_portc *portc = audio_engines[dev]->portc;
  int i, sgd_ptr;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  unsigned char tmp;
  oss_native_word flags;

  if (audio_engines[dev]->dmap_out->dmabuf_phys == 0)
    return -ENOSPC;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  tmp = 0x81;			/* Auto start at EOL, interrupt on FLAG */

  if (portc->bits != AFMT_U8)
    tmp |= 0x20;
  if (portc->channels != 1)
    tmp |= 0x10;
  OUTB (devc->osdev, tmp, devc->base + 0x12);

  ac97_recrate (&devc->ac97devc, portc->speed);

  sgd_ptr = devc->rec_sgd_ptr;
  for (i = 0; i < dmap->nfrags; i++)
    {
      if (sgd_ptr >= SGD_TOTAL_SIZE)
	{
	  cmn_err (CE_WARN, "VIA97: Out of Record SGD entries\n");
	  return -ENOSPC;
	}

      devc->SGD_table[sgd_ptr].phaddr =
	dmap->dmabuf_phys + (i * dmap->fragment_size);
      devc->SGD_table[sgd_ptr].flags = 0x40000000 | dmap->fragment_size;

      sgd_ptr++;
    }

  devc->SGD_table[sgd_ptr - 1].flags |= 0x80000000;	/* EOL */
  devc->rec_sgd_phys =
    devc->SGD_table_phys + devc->rec_sgd_ptr * sizeof (SGD_entry);
  OUTL (devc->osdev, devc->rec_sgd_phys, devc->base + 0x14);
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static int
via97_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  via97_devc *devc = audio_engines[dev]->devc;
  via97_portc *portc = audio_engines[dev]->portc;
  int i, sgd_ptr;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  unsigned char tmp;
  oss_native_word flags;

  if (audio_engines[dev]->dmap_out->dmabuf_phys == 0)
    return -ENOSPC;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  tmp = 0x81;			/* Auto start at EOL, interrupt on FLAG */

  if (portc->bits != AFMT_U8)
    tmp |= 0x20;
  if (portc->channels != 1)
    tmp |= 0x10;
  OUTB (devc->osdev, tmp, devc->base + 0x02);

  ac97_playrate (&devc->ac97devc, portc->speed);

  sgd_ptr = devc->play_sgd_ptr;

  for (i = 0; i < dmap->nfrags; i++)
    {
      if (sgd_ptr >= SGD_TOTAL_SIZE)
	{
	  cmn_err (CE_WARN, "VIA97: Out of Playback SGD entries\n");
	  return -ENOSPC;
	}

      devc->SGD_table[sgd_ptr].phaddr =
	dmap->dmabuf_phys + (i * dmap->fragment_size);
      devc->SGD_table[sgd_ptr].flags = 0x40000000 | dmap->fragment_size;

      sgd_ptr++;
    }

  devc->SGD_table[sgd_ptr - 1].flags |= 0x80000000;	/* EOL */
  devc->play_sgd_phys =
    devc->SGD_table_phys + devc->play_sgd_ptr * sizeof (SGD_entry);
  OUTL (devc->osdev, devc->play_sgd_phys, devc->base + 0x04);

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

static int
via97_alloc_buffer (int dev, dmap_t * dmap, int direction)
{
  int err, i;
  via97_devc *devc = audio_engines[dev]->devc;

  if (dmap->dmabuf != NULL)
    return 0;
  if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0)
    return err;

  if (dmap->buffsize > BUFFER_SIZE)
    {
      cmn_err (CE_WARN, "Too large DMA buffer (%d/%d) - truncated\n",
	       dmap->buffsize, BUFFER_SIZE);
      dmap->buffsize = BUFFER_SIZE;
    }

  return 0;
}

static int
via97_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
/*
 * Unfortunately the VIA chip seems to raise interrupt about 32 bytes before
 * the DMA pointer moves to a new fragment. This means that the bytes value
 * returned by SNDCTL_DSP_GET[]PTR will be bogus during few samples before
 * the pointer wraps back to the beginning of buffer.
 *
 * If mmap() is not being used this routine will return 0 during this period.
 */
  via97_devc *devc = audio_engines[dev]->devc;
  unsigned int ptr, sgd;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  if (direction == PCM_ENABLE_OUTPUT)
    {
      unsigned int p;
      p = ptr = INL (devc->osdev, devc->base + 0x0c) & 0xffffff;
      sgd =
	((INL (devc->osdev, devc->base + 0x04) - devc->play_sgd_phys) / 8) -
	1;

      ptr = dmap->fragment_size - ptr;
      ptr = ptr + (sgd * dmap->fragment_size);
//cmn_err(CE_CONT, "%d/%d/%d\n", p, sgd, ptr);
    }
  else
    {
      ptr = INL (devc->osdev, devc->base + 0x1c) & 0xffffff;
      sgd =
	((INL (devc->osdev, devc->base + 0x14) - devc->rec_sgd_phys) / 8) - 1;

      ptr = dmap->fragment_size - ptr;
      ptr = ptr + (sgd * dmap->fragment_size);
    }

  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr;
}

static const audiodrv_t via97_audio_driver = {
  via97_audio_open,
  via97_audio_close,
  via97_audio_output_block,
  via97_audio_start_input,
  via97_audio_ioctl,
  via97_audio_prepare_for_input,
  via97_audio_prepare_for_output,
  via97_audio_reset,
  NULL,
  NULL,
  via97_audio_reset_input,
  via97_audio_reset_output,
  via97_audio_trigger,
  via97_audio_set_rate,
  via97_audio_set_format,
  via97_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  via97_alloc_buffer,
  NULL,				/* via97_free_buffer, */
  NULL,
  NULL,
  via97_get_buffer_pointer
};

#ifdef OBSOLETED_STUFF
/*
 * This device has "ISA style" MIDI and FM subsystems. Such devices don't
 * use PCI config space for the I/O ports and interrupts. Instead the driver
 * needs to allocate proper resources itself. This functionality is no longer
 * possible. For this reason the MIDI and FM parts are not accessible.
 */
static void
attach_mpu (via97_devc * devc)
{
  struct address_info hw_config;

  hw_config.io_base = devc->mpu_base;
  hw_config.irq = -devc->irq;
  hw_config.dma = -1;
  hw_config.dma2 = -1;
  hw_config.always_detect = 0;
  hw_config.name = "VIA97 external MIDI";
  hw_config.driver_use_1 = 0;
  hw_config.driver_use_2 = 0;
  hw_config.osdev = devc->osdev;
#ifdef CREATE_OSP
  CREATE_OSP (hw_config.osdev);
#endif
  hw_config.card_subtype = 0;
#if 1
  if (!probe_uart401 (&hw_config))
    {
      cmn_err (CE_WARN, "VIA97: MPU-401 was not detected\n");
      return;
    }
#endif
  DDB (cmn_err (CE_WARN, "VIA97: MPU-401 detected - Good\n"));
  attach_uart401 (&hw_config);
  devc->mpu_attached = 1;
}

static void
unload_mpu (via97_devc * devc)
{
  struct address_info hw_config;

  if (devc == NULL || !devc->mpu_attached)
    return;

  devc->mpu_attached = 0;
  hw_config.io_base = devc->mpu_base;
  hw_config.irq = devc->irq;
  hw_config.dma = -1;
  hw_config.dma2 = -1;
  hw_config.always_detect = 0;
  hw_config.name = "VIA97";
  hw_config.driver_use_1 = 0;
  hw_config.driver_use_2 = 0;
  hw_config.osdev = devc->osdev;
#ifdef CREATE_OSP
  CREATE_OSP (hw_config.osdev);
#endif
  hw_config.card_subtype = 0;

  unload_uart401 (&hw_config);

}
#endif

static int
init_via97 (via97_devc * devc)
{
  int my_mixer, adev, opts, i;
  int first_dev = 0;
  oss_native_word phaddr;


/*
 * Allocate the SGD buffers
 */

  if (devc->SGD_table == NULL)
    {
      devc->SGD_table =
	CONTIG_MALLOC (devc->osdev, SGD_ALLOC, MEMLIMIT_32BITS, &phaddr);

      if (devc->SGD_table == NULL)
	return -ENOSPC;
      devc->SGD_table_phys = phaddr;
    }

  /*
   * Allocate SGD entries for recording and playback.
   */
  devc->rec_sgd_ptr = 0;
  devc->play_sgd_ptr = SGD_SIZE;
/*
 * Init mixer
 */
  my_mixer =
    ac97_install (&devc->ac97devc, "VIA82C686 AC97 Mixer", ac97_read,
		  ac97_write, devc, devc->osdev);
  if (my_mixer == -1)
    return 0;			/* No mixer */

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

  /* enable variable rate */
  ac97_write (devc, 0x2a, 0x01);

#ifdef OBSOLETED_STUFF
  DDB (cmn_err (CE_WARN, "Probing UART401 at 0x%x\n", devc->mpu_base));
  attach_mpu (devc);
#endif

  for (i = 0; i < 2; i++)
    {
      via97_portc *portc = &devc->portc[i];
      char tmp_name[100];

      opts = ADEV_AUTOMODE;

      if (!ac97_varrate (&devc->ac97devc))
	{
	  opts |= ADEV_FIXEDRATE;
	}

      if (i == 0)
	{
	  opts |= ADEV_DUPLEX;
	  strcpy (tmp_name, "VIA 82C686 AC97 Controller");
	}
      else
	{
	  opts |= ADEV_DUPLEX | ADEV_SHADOW;
	  strcpy (tmp_name, "VIA 82C686 AC97 Controller");
	}

      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&via97_audio_driver,
					sizeof (audiodrv_t),
					opts,
					AFMT_U8 | AFMT_S16_LE, 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]->min_rate = 8000;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  portc->open_mode = 0;
	  portc->audio_enabled = 0;
	  portc->audiodev = adev;
	  audio_engines[adev]->min_block = MIN_BLOCK;
	  audio_engines[adev]->max_block = 4 * 1024;
	  audio_engines[adev]->max_fragments = SGD_SIZE;
	  if (opts & ADEV_FIXEDRATE)
	    {
	      audio_engines[adev]->fixed_rate = 48000;
	      audio_engines[adev]->min_rate = 48000;
	      audio_engines[adev]->max_rate = 48000;
	    }
	}
    }
  return 1;
}

int
via97_attach (oss_device_t * osdev)
{
  unsigned char tmp, pci_irq_line, pci_revision /*, pci_latency */ ;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  int mpu_base;
  via97_devc *devc;

  DDB (cmn_err (CE_WARN, "Entered VT82C686 probe routine\n"));

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

  if (vendor != VIA_VENDOR_ID || device != VIA_82C686)
    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_ioaddr);

  if (pci_ioaddr == 0)
    {
      cmn_err (CE_WARN, "VIA97: undefined I/O address.\n");
      return 0;
    }

  if (pci_irq_line == 0)
    {
      cmn_err (CE_WARN, "VIA97: IRQ not set (%d).\n", pci_irq_line);
      return 0;
    }

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

  devc->osdev = osdev;
  osdev->devc = devc;
  devc->chip_name = "VIA VT82C686";

  /* Enable codec, etc */

  pci_write_config_byte (osdev, 0x41, 0xc0);
  oss_udelay (10);
  pci_read_config_byte (osdev, 0x41, &tmp);
  pci_write_config_byte (osdev, 0x41, tmp | 0x0c);
  oss_udelay (10);

  /* setup game port/MIDI */
  pci_write_config_byte (osdev, 0x42, 0x2a);
  /* disable FM io */
  pci_write_config_byte (osdev, 0x48, 0x00);

  /* Enable interrupt on FLAG and on EOL */
  tmp = INB (devc->osdev, devc->base + 0x22);
  OUTB (devc->osdev, tmp | 0x83, devc->base + 0x22);


  /* Enable MPU401 */
  pci_read_config_byte (osdev, 0x8, &tmp);
  if ((tmp & 0xff) >= 0x20)
    {
      pci_read_config_byte (osdev, 0x42, &tmp);
      pci_write_config_byte (osdev, 0x42, tmp & 0x3f);
    }

  pci_read_config_byte (osdev, 0x43, &tmp);
  switch ((tmp & 0x0c) >> 2)
    {
    case 0:
      mpu_base = 0x300;
      break;
    case 1:
      mpu_base = 0x310;
      break;
    case 2:
      mpu_base = 0x320;
      break;
    case 3:
      mpu_base = 0x330;
      break;
    }

  /* map PCI IO address space */
  devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);
  /* Remove I/O space marker in bit 0. */
  devc->base &= ~0x3;

  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  devc->irq = pci_irq_line;
  devc->mpu_base = mpu_base;
  devc->SGD_table = NULL;
  devc->mpu_attached = 0;
  devc->open_mode = 0;

  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, via97intr, NULL) < 0)
    {
      cmn_err (CE_WARN, "VIA97: Unable to register interrupts\n");
      return 0;
    }

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


int
via97_detach (oss_device_t * osdev)
{
  via97_devc *devc = (via97_devc *) osdev->devc;


  if (oss_disable_device (devc->osdev) < 0)
    return 0;

  OUTB (devc->osdev, 0x40, devc->base + 0x01);
  OUTB (devc->osdev, 0x40, devc->base + 0x11);
  OUTB (devc->osdev, 0, devc->base + 0x02);
  OUTB (devc->osdev, 0, devc->base + 0x12);
  OUTL (devc->osdev, 0, devc->base + 0x04);
  OUTL (devc->osdev, 0, devc->base + 0x14);
  OUTL (devc->osdev, 0, devc->base + 0x22);
  oss_udelay (30);

#ifdef OBSOLETED_STUFF
  if (devc->mpu_attached)
    unload_mpu (devc);
#endif

  oss_unregister_interrupts (devc->osdev);

  if (devc->SGD_table != NULL)
    {
      CONTIG_FREE (devc->osdev, devc->SGD_table, SGD_ALLOC);
      devc->SGD_table = NULL;
    }

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

  oss_unregister_device (devc->osdev);
  return 1;
}
