/*
 * Purpose: VIA ENVY24HT chipset driver.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#include "oss_envy24ht_cfg.h"
#include <oss_pci.h>

#define SUPPORTED_FORMAT AFMT_S32_LE

#include <spdif.h>
#include "envy24ht.h"

/* extern envy24ht_auxdrv_t envy24ht_viaref_auxdrv; */
extern envy24ht_auxdrv_t envy24ht_ac97_auxdrv;
extern envy24ht_auxdrv_t envy24ht_revo51_auxdrv;
extern envy24ht_auxdrv_t envy24ht_revo71_auxdrv;
extern envy24ht_auxdrv_t envy24ht_aureon_auxdrv;
extern envy24ht_auxdrv_t envy24ht_julia_auxdrv;
extern envy24ht_auxdrv_t envy24ht_ap192_auxdrv;

#define OUTCH_NAMES "front c/l side rear"
static char channel_names[4][10] = {
  "front",
  "c/l",
  "side",
  "rear"
};

static card_spec models[] = {
  {0x17241412, "Generic Envy24PT motherboard audio", 6, 2,
   MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv},
  {0xf641270f, "Chaintech ZNF3-150", 6, 2,
   MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv},
  {0x2723270f, "Chaintech 9CJS", 6, 2,
   MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv},
  {0x50361297, "Shuttle SN25P", 6, 2,
   MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv},
  {0x020010b0, "Gainward Hollywood Envy24HT-S", 6, 2,
   MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv},
  {0x36311412, "M Audio Revolution 5.1", 6, 2,
   MF_SPDIFOUT | MF_192K, &envy24ht_revo51_auxdrv},
  {0x36301412, "M Audio Revolution 7.1", 8, 2,
   MF_SPDIFOUT | MF_192K, &envy24ht_revo71_auxdrv},
  {SSID_AUREON_SPACE, "Terratec Aureon 7.1 Space", 8, 2,
   MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv},
  {SSID_AUREON_UNIVERSE, "Terratec Aureon 7.1 Universe", 8, 2,
   MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv},
  {SSID_AUREON_SKY, "Terratec Aureon 7.1 Sky", 8, 2,
   MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv},
  {SSID_PRODIGY71, "Audiotrak Prodigy 7.1", 8, 2,
   MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv},
  {SSID_JULIA, "Ego Systems Juli@", 2, 2,
   MF_SPDIFOUT | MF_SPDIFIN | MF_192K | MF_NOAC97 | MF_MIDI,
   &envy24ht_julia_auxdrv},
  {SSID_PHASE28, "Terratec PHASE 28", 8, 2,
   MF_SPDIFOUT | MF_192K | MF_NOAC97 | MF_MIDI, &envy24ht_aureon_auxdrv},
  {SSID_AP192, "M-Audio Audiophile 192", 2, 2,
   MF_SPDIFOUT | MF_SPDIFIN | MF_192K | MF_NOAC97 | MF_MIDI,
   &envy24ht_ap192_auxdrv},
  {0x24031412, "VIA Vinyl Tremor Audio", 6, 2,
   MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv},
  /* XXX Do a separate auxdrv, to adjust for Envy24HT-S and other differences. */                
  {SSID_PRODIGYHD2, "Audiotrak Prodigy HD2", 2, 2,                                               
   MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_ap192_auxdrv},                                   
  {SSID_PRODIGYHD2_ADE, "Audiotrak Prodigy HD2 Advance DE", 2, 2,                                
   MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_ap192_auxdrv},         
  /* {0x17241412, "VIA Envy24HT reference design", 6, 2, */
  /* MF_SPDIFOUT | MF_MIDI, &envy24ht_viaref_auxdrv}, */
  {0}
};

static struct speed_sel speed_tab[] = {
  {
   8000, 0x06}
  ,
  {
   9600, 0x03}
  ,
  {
   11025, 0x0a}
  ,
  {
   12000, 0x02}
  ,
  {
   16000, 0x05}
  ,
  {
   22050, 0x09}
  ,
  {
   24000, 0x01}
  ,
  {
   32000, 0x04}
  ,
  {
   44100, 0x08}
  ,
  {
   48000, 0x00}
  ,
  {
   64000, 0x0f}
  ,
  {
   88200, 0x0b}
  ,
  {
   96000, 0x07}
  ,
  {
   176400, 0x0c}
  ,
  {
   192000, 0x0e}
  ,
  {-1, 0x10}
  ,
};

static const envy24ht_auxdrv_t dummy_auxdrv = { NULL };

static int
ac97_read (void *devc_, int wAddr)
{
  envy24ht_devc *devc = devc_;
  oss_native_word flags;
  int n = 0, dat;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x30);
  OUTB (devc->osdev, wAddr, devc->mt_base + 0x04);
  OUTB (devc->osdev, 0x10, devc->mt_base + 0x05);	/* Codec read */

  n = 0;
  while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x10);
  dat = INW (devc->osdev, devc->mt_base + 0x06);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return dat;
}

static int
ac97_write (void *devc_, int wAddr, int wData)
{
  envy24ht_devc *devc = devc_;
  oss_native_word flags;
  int n = 0;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x30);

  OUTB (devc->osdev, wAddr, devc->mt_base + 0x04);
  OUTW (devc->osdev, wData, devc->mt_base + 0x06);
  OUTB (devc->osdev, 0x20, devc->mt_base + 0x05);	/* Codec write */

  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return 0;
}

static __inline__ int
input_avail (envy24ht_devc * devc)
{
  unsigned char status;

  status = INB (devc->osdev, devc->ccs_base + 0x0b);
  return status & 0x1f;		/* Number of bytes in RX queue */
}

static __inline__ int
output_ready (envy24ht_devc * devc)
{
  unsigned char status;

  status = INB (devc->osdev, devc->ccs_base + 0x0a);
  return (31 - (status & 0x1f)) > 0;	/* Number of free bytes in TX queue */
}

static __inline__ void
midi_cmd (envy24ht_devc * devc, unsigned char cmd)
{
  OUTB (devc->osdev, cmd, devc->ccs_base + 0x0d);
}

static __inline__ int
midi_read (envy24ht_devc * devc)
{
  return INB (devc->osdev, devc->ccs_base + 0x0c);
}

static __inline__ void
midi_write (envy24ht_devc * devc, unsigned char byte)
{
  OUTB (devc->osdev, byte, devc->ccs_base + 0x0c);
}

static void reset_midi (envy24ht_devc * devc);
static void enter_uart_mode (envy24ht_devc * devc);

static void
midi_input_loop (envy24ht_devc * devc)
{
  int n = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  while (input_avail (devc) && n++ < 33)
    {
      unsigned char c = midi_read (devc);

      devc->input_byte = c;
      if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
	devc->midi_input_intr (devc->midi_dev, c);
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static void
midiintr (envy24ht_devc * devc)
{
  int status;

  status = INB (devc->osdev, devc->ccs_base + 0x02);
  if (status & 0x80)
    midi_input_loop (devc);
  if ((status & 0x20))
    {
      OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) | 0x20,
	    devc->ccs_base + 0x01);

#if 0
      if (devc->midi_output_intr)
	devc->midi_output_intr (devc->midi_dev);
#endif
    }
}

/*ARGSUSED*/
static int
midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
	   oss_midi_inputbuf_t inputbuf, oss_midi_outputintr_t outputintr)
{
  envy24ht_devc *devc = (envy24ht_devc *) midi_devs[dev]->devc;
  int n = 0, tmp;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (devc->midi_opened & mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }
  devc->midi_opened |= mode;

  if (mode & OPEN_READ)
    {
      while (n++ < 33 && input_avail (devc))
	midi_read (devc);

      devc->midi_input_intr = inputbyte;
      devc->midi_output_intr = outputintr;
    }
  enter_uart_mode (devc);
  tmp = INB (devc->osdev, devc->ccs_base + 0x01);
  if (mode & OPEN_READ)
    tmp &= ~0x80;
  OUTB (devc->osdev, tmp, devc->ccs_base + 0x01);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
midi_close (int dev, int mode)
{
  envy24ht_devc *devc = (envy24ht_devc *) midi_devs[dev]->devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  reset_midi (devc);
  enter_uart_mode (devc);
  reset_midi (devc);
  OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) | 0xa0,
	devc->ccs_base + 0x01);
  devc->midi_opened &= ~mode;
  devc->midi_input_intr = NULL;
  devc->midi_output_intr = NULL;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

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

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (!output_ready (devc))
    {
      OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) & ~0x20,
	    devc->ccs_base + 0x01);
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return 0;
    }

  midi_write (devc, midi_byte);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 1;
}

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

static midi_driver_t envy24ht_midi_driver = {
  midi_open,
  midi_close,
  midi_ioctl,
  midi_out
};

static void
enter_uart_mode (envy24ht_devc * devc)
{
  devc->input_byte = 0;
  midi_cmd (devc, 1);
}

void
attach_midi (envy24ht_devc * devc)
{
  char name[128];
  enter_uart_mode (devc);

  sprintf (name, "%s input", devc->model_data->product);
  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "ENVY24HT", name, &envy24ht_midi_driver, sizeof (midi_driver_t),
					MFLAG_INPUT, devc, devc->osdev);
  sprintf (name, "%s output", devc->model_data->product);
  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "ENVY24HT", name, &envy24ht_midi_driver, sizeof (midi_driver_t),
					MFLAG_OUTPUT, devc, devc->osdev);
  devc->midi_opened = 0;
  devc->midi_attached = 1;
}

static void
reset_midi (envy24ht_devc * devc)
{
  /*
   * Send the RESET command. Try again if no success at the first time.
   */

  midi_cmd (devc, 0);
}

void
unload_midi (envy24ht_devc * devc)
{
  if (devc->midi_attached)
    reset_midi (devc);
  devc->midi_attached = 0;
}

static int
envy24htintr (oss_device_t * osdev)
{
  envy24ht_devc *devc = osdev->devc;
  int port, status, mt_status, serviced = 0;

  status = INB (devc->osdev, devc->ccs_base + 0x02);
  if (status != 0)
    serviced = 1;

  if (status & 0xa0)
    midiintr (devc);

/*
 * Handle audio interrupts 
 */
  mt_status = INB (devc->osdev, devc->mt_base + 0x00);

  if (mt_status & 0x08)		/* FIFO underrun/overrun */
    {
      OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x1A),
	    devc->mt_base + 0x1A);
      serviced = 1;
    }

  if ((status & 0x10) || mt_status != 0)
    {

      for (port = 0; port < devc->nr_outdevs; port++)
	{
	  envy24ht_portc *portc = &devc->play_portc[port];

	  if (mt_status & portc->mask)
	    {
	      oss_audio_outputintr (portc->dev, 1);
	    }
	}

      for (port = 0; port < devc->nr_indevs; port++)
	{
	  envy24ht_portc *portc = &devc->rec_portc[port];

	  if (mt_status & portc->mask)
	    {
	      oss_audio_inputintr (portc->dev, 0);
	    }
	}
    }
  OUTB (devc->osdev, mt_status, devc->mt_base + 0x00);
  OUTB (devc->osdev, status, devc->ccs_base + 0x02);

  return serviced;
}

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

static mixer_driver_t envy24ht_mixer_driver = {
  envy24ht_mixer_ioctl
};

static int
envy24ht_set_route (int dev, int ctrl, unsigned int cmd, int value)
{
  envy24ht_devc *devc = mixer_devs[dev]->hw_devc;
  unsigned int tmp;
  oss_native_word flags;

  if (ctrl < 0 || ctrl > 8)
    return OSS_EINVAL;

  if (cmd == SNDCTL_MIX_READ)
    {
      tmp = INL (devc->osdev, devc->mt_base + 0x2c);

      switch (ctrl)
	{
	case 0:
	  tmp >>= 8;
	  break;
	case 2:
	  tmp >>= 11;
	  break;
	case 4:
	  tmp >>= 14;
	  break;
	case 6:
	  tmp >>= 17;
	  break;
	}

      tmp = (tmp & 0x03) >> 1;

      if (tmp == 3)
	return 2;
      return tmp;
    }
  else if (cmd == SNDCTL_MIX_WRITE)
    {
      int left_shift = 0, right_shift = 0;

      static const unsigned char cnv_tab[3] = { 0x00, 0x02, 0x06 };
      if (value < 0 || value > 2)
	return OSS_EINVAL;

      MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
      tmp = INL (devc->osdev, devc->mt_base + 0x2c);

      switch (ctrl)
	{
	case 0:
	  left_shift = 8;
	  right_shift = 20;
	  break;
	case 2:
	  left_shift = 11;
	  right_shift = 23;
	  break;
	case 4:
	  left_shift = 14;
	  right_shift = 26;
	  break;
	case 6:
	  left_shift = 17;
	  right_shift = 29;
	  break;
	case 8:
	  left_shift = 0;
	  right_shift = 3;
	  break;
	}

      tmp &= ~(0x7 << left_shift);
      tmp &= ~(0x7 << right_shift);
      tmp |= cnv_tab[value] << left_shift;
      if (ctrl != 8)
	tmp |= (cnv_tab[value] + 1) << right_shift;
      else
	tmp |= cnv_tab[value] << right_shift;

      OUTL (devc->osdev, tmp, devc->mt_base + 0x2c);
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

      return value;
    }
  return OSS_EINVAL;
}

static int
read_peak (envy24ht_devc * devc, int ch)
{
  int tmp;

  if (ch >= 22)
    return 0;

  OUTB (devc->osdev, ch, devc->mt_base + 0x3e);
  tmp = INB (devc->osdev, devc->mt_base + 0x3f);

  return tmp;
}

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

  envy24ht_devc *devc = mixer_devs[dev]->hw_devc;

  int i, orign, n = -1, left = 0, right = 0;

  for (i = 0; i < 12 && n == -1; i++)
    if (ctrl & (1 << i))
      n = i;

  if (n == -1)
    return OSS_EINVAL;

  orign = n;
  if (ctrl & 0x80000000)
    n += 10;			/* Recording stream */

  if (cmd == SNDCTL_MIX_READ)
    {
      left = read_peak (devc, n);
      if (ctrl & (1 << (orign + 1)))	/* Stereo mode? */
	right = read_peak (devc, n + 1);
      else
	right = left;

      left = peak_cnv[left];
      right = peak_cnv[right];
      return left | (right << 8);
    }

  return OSS_EINVAL;
}

/*ARGSUSED*/ 
static int
create_peak_mixer (int dev, envy24ht_devc * devc, int root)
{
  int i, mask = devc->outportmask, group, err, num, skip;
  int nc = devc->nr_play_channels;
  char tmp[64];

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

  skip = 2;

  for (i = 0; i < nc; i += skip)
    {

      num = 1 << i;
      if (!(mask & num))
	continue;		/* Not present */

      {
	if (i == 8)
	  strcpy (tmp, "ENVY24_SPDIFOUT");
	else
	  sprintf (tmp, devc->channel_names[i / 2], i + 1, i + 2);

	num |= 1 << (i + 1);
	if ((err = mixer_ext_create_control (dev, group,
					     num, envy24ht_get_peak,
					     MIXT_STEREOPEAK,
					     tmp, 144,
					     MIXF_READABLE | MIXF_DECIBEL)) <
	    0)
	  return err;
      }
    }

  mask = devc->inportmask;
  for (i = 0; i < devc->nr_rec_channels; i += skip)
    {

      num = 1 << i;
      if (!(mask & num))
	continue;		/* Not present */

      num |= 0x80000000;	/* Input flag */

      {
	if (i == 8)
	  strcpy (tmp, "ENVY24_SPDIFIN");
	else
	  strcpy (tmp, "ENVY24_IN");

	num |= 1 << (i + 1);
	if ((err = mixer_ext_create_control (dev, group,
					     num, envy24ht_get_peak,
					     MIXT_STEREOPEAK,
					     tmp, 144,
					     MIXF_READABLE | MIXF_DECIBEL)) <
	    0)
	  return err;
      }
    }

  return 0;
}

/*ARGSUSED*/ 
static int
create_rout_mixer (int dev, envy24ht_devc * devc, int root)
{
  int i, mask = devc->outportmask, group, ret, num;
  char *name;

  if ((group =
       mixer_ext_create_group_flags (dev, 0, "ENVY24_ROUTE", MIXF_FLAT)) < 0)
    return group;

  for (i = 0; i < 8; i += 2)
    {

      num = 1 << i;
      if (!(mask & num))
	continue;		/* Not present */

      name = devc->channel_names[i / 2];

      if ((ret = mixer_ext_create_control (dev, group,
					   i,
					   envy24ht_set_route,
					   MIXT_ENUM, name, 3,
					   MIXF_READABLE |
					   MIXF_WRITEABLE)) < 0)
	return ret;
      mixer_ext_set_strings (dev, ret, "DMA ANALOGIN DIGITALIN", 0);
    }

  if (devc->model_data == NULL)
    {
      cmn_err (CE_CONT, "Internal error: No model data\n");
      return 0;
    }

  mask = devc->inportmask;

  if (devc->model_data->flags & MF_SPDIFOUT)
    {
      if ((ret = mixer_ext_create_control (dev, group,
					   8, envy24ht_set_route,
					   MIXT_ENUM,
					   "ENVY24_SPDIFOUT", 3,
					   MIXF_READABLE |
					   MIXF_WRITEABLE)) < 0)
	return ret;
    }

  return 0;
}

/*
 * S/PDIF lowlevel driver
 */
/*ARGSUSED*/ 
static int
default_reprogram_device (void *_devc, void *_portc,
			  oss_digital_control * ctl, unsigned int mask)
{
  unsigned char c;
  unsigned short cbits = 0;
  envy24ht_devc *devc = _devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  cbits |= (ctl->rate_bits & 0x0f) << 12;	/* Sample rate */

  if (ctl->out_vbit == VBIT_ON)
    cbits |= 0x8000;		/* Turn on the V bit */

  cbits |= ctl->cbitout[0] & 0x07;	/* Consumer/pro, audio/data, copyright */
  cbits |= (!!(ctl->cbitout[1] & 0x80)) << 11;	/* Generation level */
  cbits |= (ctl->cbitout[1] & 0x7f) << 4;	/* Category code */
  cbits |= (ctl->emphasis_type & 1) << 3;	/* Pre-emphasis */

  if (cbits != INW (devc->osdev, devc->mt_base + 0x3c))
    {
      c = INB (devc->osdev, devc->ccs_base + 0x07);
      OUTB (devc->osdev, c & ~0x80, devc->ccs_base + 0x07);	/* Disable S/PDIF transmitter */
      OUTW (devc->osdev, cbits, devc->mt_base + 0x3c);
      OUTB (devc->osdev, c | 0x80, devc->ccs_base + 0x07);	/* (Re)enable S/PDIF transmitter */
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}

spdif_driver_t default_spdif_driver = {
/* reprogram_device: */ default_reprogram_device,
};


static void
setup_sample_rate (envy24ht_devc * devc)
{
  unsigned char bits;
  int change = 0;

  devc->speedbits = bits = speed_tab[devc->pending_speed_sel].speedbits;
  if (devc->speed != speed_tab[devc->pending_speed_sel].speed)
    change = 1;
  devc->speed = devc->pending_speed =
    speed_tab[devc->pending_speed_sel].speed;
  mixer_devs[devc->mixer_dev]->modify_counter++;

  if (change)
    {
      oss_spdif_setrate (&devc->spdc, devc->speed);

      if (devc->model_data->svid == SSID_JULIA)
	goto JULIA;

      if (devc->syncsource != 0)	/* External sync */
	bits |= 0x10;

      if (devc->speed > 120000)
	{
	  OUTB (devc->osdev, 0x08, devc->mt_base + 0x02);	/* 128x I2S setup */
	}
      else
	{
	  OUTB (devc->osdev, 0x00, devc->mt_base + 0x02);	/* 256x I2S setup */
	}

      OUTB (devc->osdev, bits & 0x0f, devc->mt_base + 0x01);	/* Sampling rate */
JULIA:
      if (devc->auxdrv->set_rate)
	devc->auxdrv->set_rate (devc);
    }
}

static int
envy24ht_set_control (int dev, int ctrl, unsigned int cmd, int value)
{
  envy24ht_devc *devc = mixer_devs[dev]->hw_devc;
  oss_native_word flags;

  if (cmd == SNDCTL_MIX_READ)
    switch (ctrl)
      {
      case 1:
	return devc->pending_speed_sel;
	break;

      case 2:
	return devc->syncsource;
	break;

      case 3:
	return devc->use_src;
	break;

      case 5:
	return devc->ratelock;
	break;

      case 6:
	return devc->speed;
	break;

      case 7:
	return 1;

      default:
	return OSS_EIO;
      }

  if (cmd == SNDCTL_MIX_WRITE)
    switch (ctrl)
      {
      case 1:
	if (value < 0 || value > devc->max_ratesel)
	  return OSS_EIO;
	MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
	if (devc->configured_rate_sel != value)
	  {
	    devc->configured_rate_sel = value;
	    if (devc->open_count < 1)
	      {
		devc->pending_speed_sel = devc->configured_rate_sel;
		setup_sample_rate (devc);
	      }
	  }
	MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	return devc->configured_rate_sel;
	break;

      case 2:
	if (value < 0 || value > 2)
	  return OSS_EIO;
	devc->syncsource = value;
	if (devc->model_data->svid == SSID_JULIA)
	    devc->auxdrv->private1 (devc, value);
	return devc->syncsource;
	break;

      case 3:
	devc->use_src = !!value;
	return devc->use_src;
	break;

      case 5:
	return devc->ratelock = !!value;
	break;

      case 6:
	return devc->speed;
	break;

      case 7:
	return 1;
	break;

      default:
	return OSS_EIO;
      }

  return OSS_EINVAL;
}

static int
envy24ht_mix_init (int dev)
{
  envy24ht_devc *devc = mixer_devs[dev]->hw_devc;
  int group, err, n;

  if ((group =
       mixer_ext_create_group_flags (dev, 0, "ENVY24", MIXF_FLAT)) < 0)
    return group;

  if ((err = create_peak_mixer (dev, devc, group)) < 0)
    return err;

  if ((err = create_rout_mixer (dev, devc, group)) < 0)
    return err;

  n = devc->max_ratesel + 1;
  if ((err = mixer_ext_create_control (dev, group,
				       1, envy24ht_set_control,
				       MIXT_ENUM,
				       "ENVY24_RATE", n,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  mixer_ext_set_strings (dev, err, 
   "8000 9600 11025 12000 16000 22050 24000 32000 44100 48000 64000 88200 96000 176400 192000", 0);

  if ((err = mixer_ext_create_control (dev, group,
				       2, envy24ht_set_control,
				       MIXT_ENUM,
				       "ENVY24_SYNC", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       3, envy24ht_set_control,
				       MIXT_ONOFF,
				       "ENVY24_SRC", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       5, envy24ht_set_control,
				       MIXT_ONOFF,
				       "ENVY24_RATELOCK", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       6, envy24ht_set_control,
				       MIXT_VALUE,
				       "ENVY24_ACTRATE", 192000,
				       MIXF_READABLE)) < 0)
    return err;

  if (devc->auxdrv->mixer_init)
    if ((err = devc->auxdrv->mixer_init (devc, dev, 0)) < 0)
      return err;

  if ((err = oss_spdif_mix_init (&devc->spdc)) < 0)
    return err;

  return 0;
}

static int
eeprom_read (envy24ht_devc * devc, int pos)
{
  int i, status;

  for (i = 0; i < 0x10000; i++)
    {
      status = INB (devc->osdev, devc->ccs_base + 0x13);
      if (!(status & 1))
	break;

    }

  OUTB (devc->osdev, pos, devc->ccs_base + 0x11);	/* Offset */
  OUTB (devc->osdev, 0xa0, devc->ccs_base + 0x10);	/* EEPROM read */

  for (i = 0; i < 0x10000; i++)
    {
      status = INB (devc->osdev, devc->ccs_base + 0x13);
      if (!(status & 1))
	break;

    }

  oss_udelay (1);
  return INB (devc->osdev, devc->ccs_base + 0x12);
}

static void
envy24pt_init (envy24ht_devc * devc)
{
  int gpio;

  gpio = INW (devc->osdev, devc->ccs_base + 0x14);
  gpio |= INB (devc->osdev, devc->ccs_base + 0x1e) << 16;

#if 0
#define GPBIT(nn) !!(1<<nn)
  cmn_err (CE_CONT, "GPIO=%06x\n", gpio);
  cmn_err (CE_CONT, "With SPDIF_IN 'optical' connector: %d\n", GPBIT (1));
  cmn_err (CE_CONT, "With SPDIF_IN 'coaxial' connector: %d\n", GPBIT (2));

  cmn_err (CE_CONT, "AC97 with stereo DAC for 7.1: %d\n", !GPBIT (8));
  cmn_err (CE_CONT, "Extra ADC/DAC connected to S/PDIF pins: %d\n",
	   GPBIT (9));
  cmn_err (CE_CONT, "S/PDIF connected to RDMA1: %d\n", !GPBIT (10));
  cmn_err (CE_CONT, "Smart 5.1 function supported: %d\n", GPBIT (11));
  cmn_err (CE_CONT, "De-POP function supported: %d\n", !GPBIT (12));
  cmn_err (CE_CONT, "PDMA4 DAC: 0=cs4321 1=WM8728: %d\n", GPBIT (13));
#endif

  OUTB (devc->osdev, 0x00, devc->ccs_base + 0x04);	/* System configuration */
  OUTB (devc->osdev, 0x02, devc->ccs_base + 0x05);	/* AC-link configuration */
  OUTB (devc->osdev, 0x00, devc->ccs_base + 0x06);	/* I2S configuration */
  OUTB (devc->osdev, 0x83, devc->ccs_base + 0x07);	/* S/PDIF configuration */

  /* TODO: GPIO initialization */
}

static void
julia_eeprom_init (envy24ht_devc * devc)
{
  OUTB (devc->osdev, 0x39, devc->ccs_base + 0x04);	/* System configuration */
  OUTB (devc->osdev, 0x80, devc->ccs_base + 0x05);	/* AC-link configuration */
  OUTB (devc->osdev, 0x78, devc->ccs_base + 0x06);	/* I2S configuration */
  OUTB (devc->osdev, 0xc3, devc->ccs_base + 0x07);	/* S/PDIF configuration */

  OUTW (devc->osdev, 0xffff, devc->ccs_base + 0x18);	/* GPIO direction */
  OUTW (devc->osdev, 0x0000, devc->ccs_base + 0x16);	/* GPIO write mask */
  OUTW (devc->osdev, 0x801A, devc->ccs_base + 0x14);	/* Initital bit state */
}

static int
init_eeprom_v2 (envy24ht_devc * devc)
{
  unsigned char *eeprom = (unsigned char *) &devc->eeprom;

  OUTB (devc->osdev, eeprom[6], devc->ccs_base + 0x04);
  OUTB (devc->osdev, eeprom[7], devc->ccs_base + 0x05);
  OUTB (devc->osdev, eeprom[8], devc->ccs_base + 0x06);
  OUTB (devc->osdev, eeprom[9], devc->ccs_base + 0x07);

  OUTB (devc->osdev, eeprom[10], devc->ccs_base + 0x18);
  OUTB (devc->osdev, eeprom[11], devc->ccs_base + 0x19);
  OUTB (devc->osdev, eeprom[12], devc->ccs_base + 0x1a);

  OUTB (devc->osdev, eeprom[13], devc->ccs_base + 0x16);
  OUTB (devc->osdev, eeprom[14], devc->ccs_base + 0x17);
  OUTB (devc->osdev, eeprom[15], devc->ccs_base + 0x1f);

  OUTB (devc->osdev, eeprom[16], devc->ccs_base + 0x14);
  OUTB (devc->osdev, eeprom[17], devc->ccs_base + 0x15);
  OUTB (devc->osdev, eeprom[18], devc->ccs_base + 0x1e);
  return 1;
}

static int
load_eeprom (envy24ht_devc * devc)
{
  int status, i, check;
  unsigned char *eeprom = (unsigned char *) &devc->eeprom, c;
  static const char resolutions[4] = { 16, 18, 20, 24 };

  c = 0;

  status = INB (devc->osdev, devc->ccs_base + 0x13);

  if (!(status & 0x80))
    return 0;			/* No EEPROM */

  devc->eeprom.bSize = sizeof (devc->eeprom);	/* Guess the size */

  for (i = 0; i < devc->eeprom.bSize; i++)
    {
      eeprom[i] = eeprom_read (devc, i);
      eeprom[i] = eeprom_read (devc, i);

      if (devc->eeprom.bSize > sizeof (devc->eeprom))
	devc->eeprom.bSize = sizeof (devc->eeprom);
    }
#if 1
  DDB (cmn_err (CE_CONT, "EEPROM="));
  for (i = 0; i < devc->eeprom.bSize; i++)
    DDB (cmn_err (CE_CONT, "0x%02x, ", eeprom[i]));
  DDB (cmn_err (CE_CONT, "\n"));
#endif

  check = 0;
  for (i = 0; i < 4; i++)
    {
      check <<= 8;
      check |= eeprom[i];
    }

  if (check != devc->model_data->svid)
    cmn_err (CE_CONT,
	     "Envy24 WARNING: Possible EEPROM read error %08x != %08x\n",
	     check, devc->model_data->svid);

  DDB (cmn_err (CE_CONT, "EEPROM version %d\n", devc->eeprom.bVersion));

  if (devc->eeprom.bVersion == 2)
    {
      return init_eeprom_v2 (devc);
    }

  /* Init the controller registers based on the EEPROM data */

  OUTB (devc->osdev, devc->eeprom.bCodecConfig, devc->ccs_base + 0x04);
  OUTB (devc->osdev, devc->eeprom.bACLinkConfig, devc->ccs_base + 0x05);
  OUTB (devc->osdev, devc->eeprom.bI2SID, devc->ccs_base + 0x06);
  OUTB (devc->osdev, devc->eeprom.bSpdifConfig, devc->ccs_base + 0x07);

  /* GPIO ports */

  OUTB (devc->osdev, devc->eeprom.bGPIODirection2, devc->ccs_base + 0x18);
  OUTB (devc->osdev, devc->eeprom.bGPIODirection1, devc->ccs_base + 0x19);
  OUTB (devc->osdev, devc->eeprom.bGPIODirection0, devc->ccs_base + 0x1a);

  OUTB (devc->osdev, devc->eeprom.bGPIOInitMask2, devc->ccs_base + 0x14);
  OUTB (devc->osdev, devc->eeprom.bGPIOInitMask1, devc->ccs_base + 0x15);
  OUTB (devc->osdev, devc->eeprom.bGPIOInitMask0, devc->ccs_base + 0x1f);

  OUTB (devc->osdev, devc->eeprom.bGPIOInitState2, devc->ccs_base + 0x14);
  OUTB (devc->osdev, devc->eeprom.bGPIOInitState1, devc->ccs_base + 0x15);
  OUTB (devc->osdev, devc->eeprom.bGPIOInitState0, devc->ccs_base + 0x1e);

#if 1
  DDB (cmn_err (CE_CONT, "GPIO=%02x%02x%02x (%02x%02x%02x, %02x%02x%02x)\n",
		devc->eeprom.bGPIOInitState2,
		devc->eeprom.bGPIOInitState1,
		devc->eeprom.bGPIOInitState0,
		devc->eeprom.bGPIODirection2,
		devc->eeprom.bGPIODirection1,
		devc->eeprom.bGPIODirection0,
		devc->eeprom.bGPIOInitMask2,
		devc->eeprom.bGPIOInitMask1, devc->eeprom.bGPIOInitMask0));

  c = devc->eeprom.bCodecConfig;
  switch ((c >> 6) % 0x03)
    {
    case 0:
      DDB (cmn_err (CE_CONT, "24.576MHz crystal\n"));
      break;
    case 1:
      DDB (cmn_err (CE_CONT, "49.152MHz crystal\n"));
      break;
    default:
      DDB (cmn_err (CE_CONT, "Unknown crystal frequency\n"));
    }

  if (c & 0x20)
    {
      DDB (cmn_err (CE_CONT, "Has MPU401 UART\n"));
    }
  else
    {
      DDB (cmn_err (CE_CONT, "No MPU401 UART\n"));
    }
  DDB (cmn_err
       (CE_CONT, "%d stereo ADC pairs connected\n", ((c >> 2) & 0x03) + 1));
  DDB (cmn_err (CE_CONT, "%d stereo DAC pairs connected\n", (c & 0x03) + 1));

  c = devc->eeprom.bACLinkConfig;
  DDB (cmn_err
       (CE_CONT, "Converter type: %s\n", (c & 0x80) ? "I2S" : "AC97"));
  if (!(c & 0x80))
    {
      if (!(devc->model_data->flags & MF_NOAC97))
	devc->codec_type = CODEC_AC97;

      DDB (cmn_err (CE_NOTE,
		    "AC link connection mode type: %s\n",
		    (c & 0x02) ? "packed" : "split"));
    }
  else
    {
      c = devc->eeprom.bI2SID;

      DDB (cmn_err (CE_CONT, "I2C codec has volume control/mute: %s\n",
		    (c % 0x80) ? "YES" : "NO"));
      DDB (cmn_err (CE_CONT, "I2C codec has 96 KHz S/R support: %s\n",
		    (c % 0x40) ? "YES" : "NO"));
      DDB (cmn_err (CE_CONT, "I2C codec has 192 KHz S/R support: %s\n",
		    (c % 0x08) ? "YES" : "NO"));

      DDB (cmn_err (CE_CONT,
		    "Converter resolution %d bits\n",
		    resolutions[(c >> 4) & 0x03]));
    }

  c = INB (devc->osdev, devc->ccs_base + 0x07);
  DDB (cmn_err (CE_CONT,
		"Internal S/PDIF out implemented: %s\n",
		(c & 0x40) ? "YES" : "NO"));
  DDB (cmn_err
       (CE_CONT, "Internal S/PDIF out enabled: %s\n",
	(c & 0x80) ? "YES" : "NO"));
  DDB (cmn_err
       (CE_CONT, "External S/PDIF out implemented: %s\n",
	(c & 0x01) ? "YES" : "NO"));
  DDB (cmn_err
       (CE_CONT, "S/PDIF input present: %s\n", (c & 0x02) ? "YES" : "NO"));
  DDB (cmn_err (CE_CONT, "S/PDIF chip IDs %x\n", (c >> 2) & 0x0f));
#endif

  return 1;
}

/*ARGSUSED*/ 
static void
dump_regs (envy24ht_devc * devc, char *lbl)
{
#if 0
  int i;

  cmn_err (CE_CONT, "\nDump registers: %s\n", lbl);

  for (i = 0; i < 0x20; i += 4)
    {
      if (!(i % (8 * 4)))
	cmn_err (CE_CONT, "\nCCS%02x: ", i);
      cmn_err (CE_CONT, "%08x ", INL (devc->osdev, devc->ccs_base + i));
    }
  cmn_err (CE_CONT, "\n");

  for (i = 0; i < 0x80; i += 4)
    {
      if (!(i % (8 * 4)))
	cmn_err (CE_CONT, "\nMT%02x: ", i);
      cmn_err (CE_CONT, "%08x ", INL (devc->osdev, devc->mt_base + i));
    }
  cmn_err (CE_CONT, "\n");
#endif
}

static int
verify_rate (envy24ht_devc * devc, int arg)
{
  if (devc->codec_type == CODEC_AC97 && arg > 48000)
    arg = 48000;
  if (arg > 96000 && !(devc->model_data->flags & MF_192K))
    arg = 96000;

  return arg;
}

static int
envy24ht_set_rate (int dev, int arg)
{
  envy24ht_devc *devc = audio_engines[dev]->devc;
  envy24ht_portc *portc = audio_engines[dev]->portc;
  int i, ix, diff, bestdiff;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (arg == 0)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return devc->pending_speed;
    }

  arg = verify_rate (devc, arg);
/*
 * Don't permit changing the sampling rate if we have multiple clients.
 */
  if (devc->open_count != 1 || devc->ratelock)
    {
      DDB (cmn_err (CE_CONT, "Can't set speed: open_count %d, ratelock %d\n",
		    devc->open_count, devc->ratelock));
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      if (arg != devc->pending_speed)
	{
	  audio_engines[dev]->fixed_rate = devc->speed;
	  audio_engines[dev]->min_rate = devc->speed;
	  audio_engines[dev]->max_rate = devc->speed;
	  audio_engines[dev]->flags |= ADEV_FIXEDRATE;
	}
      else
	{
	  audio_engines[dev]->min_rate = 8000;
	  audio_engines[dev]->max_rate = 192000;
	  audio_engines[dev]->flags &= ~ADEV_FIXEDRATE;
	}
      return devc->pending_speed;
    }

  if (portc->dev_flags & DF_SPDIF)
    {
      /* Allow only supported S/PDIF rates */
      if (arg < 32000)
	arg = 32000;
      if (arg > 96000)
	arg = 96000;
    }

  ix = 9;
  bestdiff = 1000000;
  i = 0;
  audio_engines[dev]->flags &= ~ADEV_FIXEDRATE;

  while (speed_tab[i].speed != -1)
    {
      diff = speed_tab[i].speed - arg;
      if (diff < 0)
	diff = -diff;
      if (diff < bestdiff)
	{
	  ix = i;
	  bestdiff = diff;
	}
      i++;
    }

  devc->pending_speed = speed_tab[ix].speed;
  devc->pending_speed_sel = ix;
  /*cmn_err(CE_CONT, "Requested sampling rate %d, got %d\n", arg, devc->pending_speed); */

  //setup_sample_rate (devc);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return devc->pending_speed;
}

static short
envy24ht_set_channels (int dev, short arg)
{
  envy24ht_portc *portc = audio_engines[dev]->portc;
  envy24ht_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;

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

  if (portc->dev_flags & DF_MULTICH)
    {
      int n = 2, ch, i, mask;

      if (arg < 2)
	arg = 2;

      arg = ((arg + 1) / 2) * 2;	/* Round to even number of channels */

      if (arg > devc->model_data->nr_outs)
	arg = devc->model_data->nr_outs;

      MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

      devc->busy_play_channels &= ~portc->used_chmask;

      for (ch = 2; ch <= arg; ch += 2)
	{
	  mask = 0;

	  for (i = 0; i < ch; i++)
	    mask |= (1 << i);

	  if (devc->busy_play_channels & mask)
	    break;
	  n = ch;
	}

      portc->channels = n;
      portc->used_chmask = 0;
      for (i = 0; i < n; i++)
	portc->used_chmask |= (1 << i);

      devc->busy_play_channels |= portc->used_chmask;
      /* MT19: Channel allocation */
      OUTB (devc->osdev, 4 - n / 2, devc->mt_base + 0x19);
      /* cmn_err(CE_CONT, "%d channels: MT19=%02x\n", n, INB(devc->osdev, devc->mt_base+0x19)); */

      if (portc->channels == 6)
	{
	  /* The fragment size must be a multiple of 6 */
	  audio_engines[dev]->min_block = 4 * 288;
	  audio_engines[dev]->max_block = 4 * 288;

	}
      else
	{
	  audio_engines[dev]->min_block = 0;
	  audio_engines[dev]->max_block = 0;
	}

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

  return portc->channels = 2;
}

/*ARGSUSED*/ 
static int
ac3_write (adev_t * adev,
	   dmap_t * dmap,
	   void *frombuf, void *tobuf, int maxspace, int *fromlen, int *tolen)
{
/*
 * This routine takes AC3 input 16 bits at time and packs them to
 * 32 bit words.
 */
  int i, l;
  unsigned short *ip;
  unsigned int *op;

  l = *fromlen * 2;
  if (l > maxspace)
    {
      l = maxspace;
    }

  *tolen = l;
  *fromlen = l / 2;
  l /= 4;

  ip = frombuf;
  op = tobuf;

  for (i = 0; i < l; i++)
    {
      *op++ = (*ip++) << 16;
    }

  return 0;
}

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

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

  if (arg == AFMT_AC3 && (portc->dev_flags & DF_AC3))
    {
      audio_engines[dev]->dmap_out->device_write = ac3_write;
      return portc->fmt = AFMT_AC3;
    }
  audio_engines[dev]->dmap_out->device_write = NULL;
  return portc->fmt = SUPPORTED_FORMAT;
}

static int
envy24ht_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  envy24ht_portc *portc = audio_engines[dev]->portc;
  envy24ht_devc *devc = audio_engines[dev]->devc;

  switch (cmd)
    {
    case SNDCTL_DSP_GET_CHNORDER:
      *(oss_uint64_t *) arg = CHNORDER_NORMAL;
      return 0;
    }

  if (portc->dev_flags & DF_SPDIF)
    {
      int ret;
      ret = oss_spdif_ioctl (&devc->spdc, portc->open_mode, cmd, arg);
      if (ret != SPDIF_NOIOCTL)
	return ret;
    }

  if (devc->auxdrv->audio_ioctl)
    return devc->auxdrv->audio_ioctl (devc, portc, cmd, arg);
  return OSS_EINVAL;
}

static void envy24ht_trigger (int dev, int state);

static void
envy24ht_reset (int dev)
{
  envy24ht_trigger (dev, 0);
}

/*ARGSUSED*/ 
static int
envy24ht_open_input (int dev, int mode, int open_flags)
{
  envy24ht_portc *portc = audio_engines[dev]->portc;
  envy24ht_devc *devc = audio_engines[dev]->devc;
  adev_p adev = audio_engines[dev];
  oss_native_word flags;

  if (mode & OPEN_WRITE)
    {
      cmn_err (CE_CONT, "Playback is not possible with %s\n", adev->devnode);
      return OSS_ENOTSUP;
    }

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode || (devc->busy_rec_channels & portc->chmask))
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }
  portc->open_mode = mode;
  devc->open_count++;
  if (devc->open_count == 1)
    {
      devc->pending_speed_sel = devc->configured_rate_sel;
    }

  if (portc->dev_flags & DF_SPDIF)
    oss_spdif_open (&devc->spdc, mode);

  portc->used_chmask = portc->chmask;
  devc->busy_rec_channels |= portc->chmask;

  if (!devc->use_src)
    adev->flags |= ADEV_NOSRC;
  else
    adev->flags &= ~ADEV_NOSRC;

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

/*ARGSUSED*/ 
static int
envy24ht_open_output (int dev, int mode, int open_flags)
{
  envy24ht_portc *portc = audio_engines[dev]->portc;
  envy24ht_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  adev_p adev = audio_engines[dev];

  if (mode & OPEN_READ)
    {
      cmn_err (CE_CONT, "Recording is not possible with %s\n", adev->devnode);
      return OSS_ENOTSUP;
    }

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode || (devc->busy_play_channels & portc->chmask))
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

  portc->open_mode = mode;
  portc->used_chmask = portc->chmask;
  devc->busy_play_channels |= portc->chmask;
  audio_engines[dev]->dmap_out->device_write = NULL;

  devc->open_count++;
  if (devc->open_count == 1)
    {
      devc->pending_speed_sel = devc->configured_rate_sel;
    }

  if (portc->dev_flags & DF_SPDIF)
    oss_spdif_open (&devc->spdc, mode);

  if (!devc->use_src)
    adev->flags |= ADEV_NOSRC;
  else
    adev->flags &= ~ADEV_NOSRC;

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

static void
envy24ht_close (int dev, int mode)
{
  envy24ht_portc *portc = audio_engines[dev]->portc;
  envy24ht_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  devc->open_count--;

  if (portc->open_mode & OPEN_READ)
    devc->busy_rec_channels &= ~portc->used_chmask;
  if (portc->open_mode & OPEN_WRITE)
    devc->busy_play_channels &= ~portc->used_chmask;
  portc->open_mode = 0;

  if (portc->dev_flags & DF_MULTICH)
    {
      OUTB (devc->osdev, 0x03, devc->mt_base + 0x19);	/* Channel allocation */
      portc->chmask = 0x003;	/* Just the front channels */
    }

  if (portc->dev_flags & DF_SPDIF)
    oss_spdif_close (&devc->spdc, mode);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

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

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

static void
envy24ht_trigger (int dev, int state)
{
  envy24ht_devc *devc = audio_engines[dev]->devc;
  envy24ht_portc *portc = audio_engines[dev]->portc;
  unsigned char enable, intrmask;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  enable = INB (devc->osdev, devc->mt_base + 0x18);
  intrmask = INB (devc->osdev, devc->mt_base + 0x03);

  if (portc->state_bits == state)	/* No change */
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return;
    }
  portc->state_bits = state;

  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  enable |= portc->mask;
	  intrmask &= ~portc->mask;
	}
      else
	{
	  enable &= ~portc->mask;
	  intrmask |= portc->mask;
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  enable |= portc->mask;
	  intrmask &= ~portc->mask;
	}
      else
	{
	  enable &= ~portc->mask;
	  intrmask |= portc->mask;
	}
    }
  OUTB (devc->osdev, enable, devc->mt_base + 0x18);
  OUTB (devc->osdev, intrmask, devc->mt_base + 0x03);

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  if (state)
    dump_regs (devc, "trigger");
}

/*ARGSUSED*/ 
static int
envy24ht_prepare_for_input (int dev, int bsize, int bcount)
{
  envy24ht_devc *devc = audio_engines[dev]->devc;
  envy24ht_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_in;
  int buffsize, fragsize;
  oss_native_word flags;

  if (audio_engines[dev]->flags & ADEV_NOINPUT)
    return OSS_EACCES;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  setup_sample_rate (devc);
  buffsize = dmap->bytes_in_use / 4 - 1;
  fragsize = dmap->fragment_size / 4 - 1;

  OUTL (devc->osdev, dmap->dmabuf_phys, portc->base + 0x00);
  OUTW (devc->osdev, buffsize, portc->base + 0x04);
  OUTW (devc->osdev, fragsize, portc->base + 0x06);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/ 
static int
envy24ht_prepare_for_output (int dev, int bsize, int bcount)
{
  envy24ht_devc *devc = audio_engines[dev]->devc;
  envy24ht_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_out;
  int buffsize, fragsize;
  oss_native_word flags;

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

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  setup_sample_rate (devc);
  buffsize = dmap->bytes_in_use / 4 - 1;
  fragsize = dmap->fragment_size / 4 - 1;

  if (portc->dev_flags & DF_MULTICH)
    {
      /* Multi ch device */
      OUTL (devc->osdev, dmap->dmabuf_phys, devc->mt_base + 0x10);
      OUTL (devc->osdev, buffsize, devc->mt_base + 0x14);
      OUTL (devc->osdev, fragsize, devc->mt_base + 0x1c);
    }
  else
    {
      OUTL (devc->osdev, dmap->dmabuf_phys, portc->base + 0x00);
      OUTW (devc->osdev, buffsize, portc->base + 0x04);
      OUTW (devc->osdev, fragsize, portc->base + 0x06);
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/ 
static int
envy24ht_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  envy24ht_portc *portc = audio_engines[dev]->portc;
  envy24ht_devc *devc;
  int pos;

  devc = audio_engines[dev]->devc;
  pos = (INW (devc->osdev, portc->base + 0x04) + 1) * 4;
  return dmap->bytes_in_use - pos;
}

static int
envy24ht_sync_control(int dev, int event, int mode)
{
  envy24ht_devc *devc = audio_engines[dev]->devc;
  envy24ht_portc *portc = audio_engines[dev]->portc;
  unsigned char enable, intrmask;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE(devc->mutex, flags);
  if(event == SYNC_PREPARE)
  {
    devc->syncstart_mask |= portc->mask;
    portc->state_bits = mode;
  }
  else if(event == SYNC_TRIGGER)
  {
    if(devc->syncstart_mask)
    {
      enable = INB (devc->osdev, devc->mt_base + 0x18);
      intrmask = INB (devc->osdev, devc->mt_base + 0x03);
      enable |= devc->syncstart_mask;
      intrmask &= ~devc->syncstart_mask;
      OUTB (devc->osdev, enable, devc->mt_base + 0x18);
      OUTB (devc->osdev, intrmask, devc->mt_base + 0x03);
      devc->syncstart_mask = 0;
    }
  }
  MUTEX_EXIT_IRQRESTORE(devc->mutex, flags);
  return 0;
}

#if 0
static int
envy24ht_check_output (int dev)
{
  int pos;
  envy24ht_devc *devc = audio_engines[dev]->devc;

  pos = envy24ht_get_buffer_pointer (dev, audio_engines[dev]->dmap_out, 0);

  cmn_err (CE_CONT,
	   "Envy24ht: Output timeout on device %d (%d, %02x, %02x)\n", dev,
	   pos, INB (devc->osdev, devc->ccs_base + 0x02), INB (devc->osdev,
							       devc->
							       ccs_base +
							       0x00));
  return OSS_EIO;
}
#endif

static audiodrv_t envy24ht_output_driver = {
  envy24ht_open_output,
  envy24ht_close,
  envy24ht_output_block,
  envy24ht_start_input,
  envy24ht_ioctl,
  envy24ht_prepare_for_input,
  envy24ht_prepare_for_output,
  envy24ht_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  envy24ht_trigger,
  envy24ht_set_rate,
  envy24ht_set_format,
  envy24ht_set_channels,
  NULL,
  NULL,
  NULL,				/* check input */
  NULL,				/* envy24ht_check_output */
  NULL,				/* envy24ht_alloc_buffer */
  NULL,				/* envy24ht_free_buffer */
  NULL,
  NULL,
  envy24ht_get_buffer_pointer,
  NULL,
  envy24ht_sync_control
};


static audiodrv_t envy24ht_input_driver = {
  envy24ht_open_input,
  envy24ht_close,
  envy24ht_output_block,
  envy24ht_start_input,
  envy24ht_ioctl,
  envy24ht_prepare_for_input,
  envy24ht_prepare_for_output,
  envy24ht_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  envy24ht_trigger,
  envy24ht_set_rate,
  envy24ht_set_format,
  envy24ht_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* envy24ht_alloc_buffer */
  NULL,				/* envy24ht_free_buffer */
  NULL,
  NULL,
  envy24ht_get_buffer_pointer,
  NULL,
  envy24ht_sync_control
};

static const int bindings[MAX_ODEV] = {
  DSP_BIND_FRONT,
  DSP_BIND_CENTER_LFE,
  DSP_BIND_SURR,
  DSP_BIND_REAR
};

static int
init_play_device (envy24ht_devc * devc, int chmask, int offset,
		  unsigned char mask, char *name, int dev_flags,
		  char *port_id, char *devfile_name)
{
  int opts, dev, formats;
  char tmp[80];
  envy24ht_portc *portc = NULL;
  int i;
  adev_p adev;

  sprintf (tmp, "%s %s out", devc->model_data->product, name);

  if (devc->nr_outdevs >= MAX_ODEV)
    {
      cmn_err (CE_CONT, "Envy24ht: Too many audio devices\n");
      return OSS_ENXIO;
    }

  opts = ADEV_AUTOMODE | ADEV_NOINPUT;

  if (dev_flags & DF_SPDIF)
    opts |= ADEV_SPECIAL;

  formats = SUPPORTED_FORMAT;
  if (dev_flags & DF_AC3)
    formats |= AFMT_AC3;

  if (dev_flags & DF_MULTICH)
    opts |= ADEV_COLD;
  else
    opts |= ADEV_SPECIAL;

  if ((dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
				   devc->osdev,
				   devc->osdev,
				   tmp,
				   &envy24ht_output_driver,
				   sizeof (audiodrv_t),
				   opts, formats, devc, -1,
				   devfile_name)) < 0)
    {
      return dev;
    }

  if (devc->first_dev == -1)
    {
      devc->first_dev = dev;
      audio_engines[dev]->outch_names = OUTCH_NAMES;
    }
  adev = audio_engines[dev];

  portc = &devc->play_portc[devc->nr_outdevs];
  for (i = 0; speed_tab[i].speed != -1; i++)
    adev->rates[adev->nrates++] = speed_tab[i].speed;

  portc->name = port_id;
  audio_engines[dev]->portc = portc;
  audio_engines[dev]->mixer_dev = devc->mixer_dev;
  audio_engines[dev]->rate_source = devc->first_dev;
  audio_engines[dev]->min_rate = 8000;
  audio_engines[dev]->max_rate = 192000;
  audio_engines[dev]->binding = bindings[devc->nr_outdevs];
  if (dev_flags & DF_SPDIF)
    audio_engines[dev]->binding = DSP_BIND_SPDIF;

  portc->dev = dev;
  portc->open_mode = 0;
  portc->fmt = SUPPORTED_FORMAT;
  portc->base = devc->mt_base + offset;
  portc->mask = mask;
  portc->dev_flags = dev_flags;
  portc->chmask = chmask;
  portc->state_bits = 0;
  portc->direction = PCM_ENABLE_OUTPUT;

  audio_engines[dev]->min_channels = 2;
  audio_engines[dev]->max_channels = 2;

  if (dev_flags & DF_SPDIF)
    audio_engines[dev]->caps |= PCM_CAP_DIGITALOUT | DSP_CH_STEREO;
  else
    {
      if (dev_flags & DF_MULTICH)
	{
	  audio_engines[dev]->caps |= PCM_CAP_ANALOGOUT;
	  audio_engines[dev]->caps |= DSP_CH_STEREO;
	  audio_engines[dev]->min_channels = 2;
	  audio_engines[dev]->max_channels = devc->model_data->nr_outs;
	}
      else
	audio_engines[dev]->caps |= PCM_CAP_ANALOGOUT | DSP_CH_STEREO;
    }
  devc->nr_outdevs++;

  return dev;
}

static int
init_rec_device (envy24ht_devc * devc, int chmask, int offset,
		 unsigned char mask, char *name, int dev_flags, char *devfile_name)
{
  int opts, dev, formats;
  adev_p adev;
  int i;
  envy24ht_portc *portc = NULL;
  char tmp[80];
  sprintf (tmp, "%s %s in", devc->model_data->product, name);

  if (devc->nr_indevs >= MAX_IDEV)
    {
      cmn_err (CE_CONT, "Envy24ht: Too many audio devices\n");
      return OSS_ENXIO;
    }

  opts = ADEV_AUTOMODE | ADEV_NOOUTPUT | ADEV_COLD;

  if (dev_flags & DF_SPDIF)
    opts |= ADEV_SPECIAL;

  formats = SUPPORTED_FORMAT;
  if (dev_flags & DF_AC3)
    formats |= AFMT_AC3;

  if ((dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
				   devc->osdev,
				   devc->osdev,
				   tmp,
				   &envy24ht_input_driver,
				   sizeof (audiodrv_t),
				   opts, formats, devc, -1,
				   devfile_name)) < 0)
    {
      return dev;
    }

  if (devc->first_dev == -1)
    devc->first_dev = dev;
  portc = &devc->rec_portc[devc->nr_indevs];
  adev = audio_engines[dev];

  for (i = 0; speed_tab[i].speed != -1; i++)
    adev->rates[adev->nrates++] = speed_tab[i].speed;

  audio_engines[dev]->portc = portc;
  audio_engines[dev]->mixer_dev = devc->mixer_dev;
  audio_engines[dev]->rate_source = devc->first_dev;
  audio_engines[dev]->min_rate = 8000;
  audio_engines[dev]->max_rate = 192000;

  portc->dev = dev;
  portc->name = "rec";
  portc->open_mode = 0;
  portc->base = devc->mt_base + offset;
  portc->mask = mask;
  portc->state_bits = 0;
  portc->fmt = SUPPORTED_FORMAT;
  portc->dev_flags = dev_flags;
  portc->chmask = chmask;
  portc->direction = PCM_ENABLE_INPUT;
  if (dev_flags & DF_SPDIF)
    audio_engines[dev]->caps |= PCM_CAP_DIGITALIN | DSP_CH_STEREO;
  else
    audio_engines[dev]->caps |= PCM_CAP_ANALOGIN | DSP_CH_STEREO;
  devc->nr_indevs++;

  return dev;
}

static void
init_devices (envy24ht_devc * devc)
{
  int front_engine, rec_engine;

  OUTB (devc->osdev, 0x03, devc->mt_base + 0x19);	/* Channel allocation */
  OUTB (devc->osdev, 0x00, devc->mt_base + 0x1b);	/* Unpause ALL channels */

  devc->first_dev = -1;

  front_engine=init_play_device (devc, 0x003, 0x10, 0x01, devc->channel_names[0],
		    DF_MULTICH, "front", "");

  if (devc->model_data->nr_outs > 2)
    init_play_device (devc, 0x00c, 0x70, 0x10, devc->channel_names[1], 0,
		      "C/LFE", "");

  if (devc->model_data->nr_outs > 4)
    init_play_device (devc, 0x030, 0x60, 0x20, devc->channel_names[2], 0,
		      "side", "");

  if (devc->model_data->nr_outs > 6)
    init_play_device (devc, 0x0c0, 0x50, 0x40, devc->channel_names[3], 0,
		      "rear", "");

  if (devc->model_data->flags & MF_SPDIFOUT)
    {
      init_play_device (devc, 0x300, 0x40, 0x80, "digital",
			DF_SPDIF | DF_AC3, "spdif", "spdout");
    }

  rec_engine = init_rec_device (devc, 0x003, 0x20, 0x02, "analog", 0, "");

  if (devc->model_data->flags & MF_SPDIFIN)
    {
      init_rec_device (devc, 0x00c, 0x30, 0x04, "digital", DF_SPDIF, "spdin");
    }

#ifdef CONFIG_OSS_VMIX
    if (rec_engine < 0)
       rec_engine = -1; /* Not available */

    if (front_engine >= 0)
       vmix_attach_audiodev(devc->osdev, front_engine, rec_engine, 0);
#endif
}

static void
install_ac97_mixer (envy24ht_devc * devc)
{
  int tmp;
  tmp = 0;

  DDB (cmn_err (CE_CONT, "Installing AC97 mixer\n"));

  devc->mixer_dev =
    ac97_install (&devc->ac97devc, devc->model_data->product, ac97_read,
		  ac97_write, devc, devc->osdev);
  if (devc->mixer_dev < 0)
    {
      cmn_err (CE_CONT, "Envy24ht: Mixer install failed\n");
      return;
    }
  ac97_init_ext (devc->mixer_dev, &devc->ac97devc, envy24ht_mix_init, 50);

#if 1
  /* AD1616 specific stuff. Check this if there is some other AC97 chip */
  /* Maybe this should be moved to ac97.c in a way or another */

  /* Turn surround dacs ON */
  tmp = ac97_read (devc, 0x2a);
  tmp &= ~0x3800;
  ac97_write (devc, 0x2a, tmp);

  tmp = ac97_read (devc, 0x5a);
  tmp &= ~0x8000;
  tmp |= 0x1800;
  ac97_write (devc, 0x5a, tmp);
#endif

#if 0
  for (tmp = 0; tmp < 0x3f; tmp += 2)
    cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp));
  for (tmp = 0x5a; tmp < 0x5d; tmp += 2)
    cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp));
  for (tmp = 0x7a; tmp < 0x7f; tmp += 2)
    cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp));
#endif
}

int
oss_envy24ht_attach (oss_device_t * osdev)
{
  envy24ht_devc *devc;
  extern int envy24ht_model;
  unsigned char pci_irq_line;
  unsigned short pci_command, vendor, device;
  unsigned int subvendor;
  unsigned int pci_ioaddr, pci_ioaddr1;
  int i, err;

  char *name = "Generic ENVY24HT";

  DDB (cmn_err (CE_CONT, "Entered Envy24HT probe routine\n"));

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

  if (vendor != ICENSEMBLE_VENDOR_ID || device != ICENSEMBLE_ENVY24HT_ID)
    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;
  MUTEX_INIT (osdev, devc->mutex, MH_DRV);
  MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1);

  pci_read_config_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr);
  pci_read_config_dword (osdev, PCI_BASE_ADDRESS_1, &pci_ioaddr1);
  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, 0x2c, &subvendor);

  DDB (cmn_err (CE_CONT,
		"Device found at I/O %x, %x\n", pci_ioaddr & ~3,
		pci_ioaddr1 & ~3));

  devc->subvendor = subvendor;

  devc->ccs_base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr) & ~0x3;
  DDB (cmn_err (CE_CONT, "CCS base %x/%lx\n", pci_ioaddr, devc->ccs_base));

  devc->mt_base = MAP_PCI_IOADDR (devc->osdev, 1, pci_ioaddr1) & ~0x3;
  DDB (cmn_err (CE_CONT, "MT base %x/%lx\n", pci_ioaddr1, devc->mt_base));

  /* Reset the chip */
  OUTB (devc->osdev, 0x81, devc->ccs_base + 0x00);
  oss_udelay (1000);

  /* Release reset */
  OUTB (devc->osdev, 0x00, devc->ccs_base + 0x00);
  oss_udelay (1000);

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

  devc->nr_outdevs = devc->nr_indevs = 0;
  i = 0;

  if ((envy24ht_model > -1)
      && (envy24ht_model < (sizeof (models) / sizeof (card_spec)) - 1))
    i = envy24ht_model;
  else
    while (models[i].svid != 0)
      {
	if (models[i].svid == subvendor)
	  {
	    name = models[i].product;
	    devc->model_data = &models[i];
	    DDB (cmn_err (CE_CONT, "Card id '%s'\n", name));

	    break;
	  }

	i++;
      }

  if (models[i].svid == 0)
    {
      cmn_err (CE_CONT, "Unknown device ID (%08x).\n", subvendor);
      cmn_err (CE_CONT, "This card may not be supported (yet).\n");
      i = 0; /* Assume AC97 based Envy23PT */
    }

  oss_register_device (osdev, name);

  if (devc->model_data == NULL)
    {
      cmn_err (CE_CONT, "Envy24ht: This card was not recognized: %08x\n",
	       subvendor);
      return 0;
    }

  /* Disable all interrupts */
  OUTB (devc->osdev, 0xff, devc->ccs_base + 0x01);
  OUTB (devc->osdev, 0xff, devc->mt_base + 0x03);

  if (devc->model_data->flags & MF_ENVY24PT)
    {
      devc->codec_type = CODEC_AC97;
      envy24pt_init (devc);
    }
  else if (devc->model_data->svid == SSID_JULIA)
    {
      julia_eeprom_init (devc);
    }
  else
    load_eeprom (devc);

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

  i = 0;
  devc->max_ratesel = 0;

  while (speed_tab[i].speed != -1)
    {
      int rate = speed_tab[i].speed;

      if (verify_rate (devc, rate) == rate)
	devc->max_ratesel = i;

      i++;
    }

  OUTB (devc->osdev, ~0x10, devc->ccs_base + 0x01);	/* Enable audio interrupts */

  if (devc->model_data->flags & MF_MIDI)
    {
      attach_midi (devc);
    }
  i = 0;
  devc->max_ratesel = 0;

  while (speed_tab[i].speed != -1)
    {
      int rate = speed_tab[i].speed;

      if (verify_rate (devc, rate) == rate)
	devc->max_ratesel = i;

      i++;
    }

  devc->syncstart_mask = 0;
  devc->speedbits = 0;
  devc->speed = 0;
  devc->pending_speed = 0;
  devc->prev_speed = 0;
  devc->pending_speed_sel = 9;
  devc->configured_rate_sel = devc->pending_speed_sel;
  devc->open_count = 0;
  memcpy (devc->channel_names, channel_names, sizeof (channel_names));

  devc->nr_play_channels = 10;
  devc->nr_rec_channels = 10;
#define setmask(m, b) m|=(1<<(b))

  devc->inportmask = 0;
  devc->outportmask = 0;
  devc->busy_play_channels = 0;
  devc->busy_rec_channels = 0;

  for (i = 0; i < devc->model_data->nr_outs; i++)
    setmask (devc->outportmask, i);
  if (devc->model_data->flags & MF_SPDIFOUT)
    {
      setmask (devc->outportmask, 8);	/* SPDIF */
      setmask (devc->outportmask, 9);	/* SPDIF */
    }
  for (i = 0; i < devc->model_data->nr_ins; i++)
    setmask (devc->inportmask, i);
  if (devc->model_data->flags & MF_SPDIFIN)
    {
      setmask (devc->inportmask, 8);	/* SPDIF */
      setmask (devc->inportmask, 9);	/* SPDIF */
    }

  if (devc->model_data->auxdrv == NULL)
    {
      devc->auxdrv = &dummy_auxdrv;
    }
  else
    {
      devc->auxdrv = devc->model_data->auxdrv;
      if (devc->auxdrv->card_init)
	devc->auxdrv->card_init (devc);
    }

  if (devc->codec_type == CODEC_AC97)
    install_ac97_mixer (devc);
  else
    {
      if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
						devc->osdev,
						devc->osdev,
						devc->model_data->
						product,
						&envy24ht_mixer_driver,
						sizeof (mixer_driver_t),
						devc)) >= 0)
	{
	  int n = 50;

	  mixer_devs[devc->mixer_dev]->hw_devc = devc;
	  mixer_ext_set_init_fn (devc->mixer_dev, envy24ht_mix_init, n);
	  mixer_devs[devc->mixer_dev]->priority = 1;	/* Possible default mixer candidate */
	}
    }

  if (devc->model_data->flags & (MF_SPDIFOUT | MF_SPDIFIN))
    {
      int err;

      if ((err = oss_spdif_install (&devc->spdc, devc->osdev,
				    &default_spdif_driver,
				    sizeof (spdif_driver_t), devc, NULL,
				    devc->mixer_dev, SPDF_OUT,
				    DIG_PASSTHROUGH | DIG_EXACT |
				    DIG_CBITOUT_LIMITED | DIG_VBITOUT |
				    DIG_PRO | DIG_CONSUMER)) != 0)
	{
	  cmn_err (CE_CONT,
		   "S/PDIF driver install failed. error %d\n", err);
	  return 0;
	}
    }
  OUTB (devc->osdev, ~0x10, devc->ccs_base + 0x01);	/* Enable audio interrupts */
  init_devices (devc);
  setup_sample_rate (devc);


  return 1;
}

int
oss_envy24ht_detach (oss_device_t * osdev)
{
  envy24ht_devc *devc = osdev->devc;

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

  /* Disable all interrupts */
  OUTB (devc->osdev, 0xff, devc->ccs_base + 0x01);
  /* disable DMA interrupt mask */
  OUTB (devc->osdev, 0xff, devc->mt_base + 0x00);

  /* Stop playback */
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x01,
	devc->mt_base + 0x18);
  oss_udelay (100);
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x01,
	devc->mt_base + 0x18);

  /* Stop recording */
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04,
	devc->mt_base + 0x18);
  oss_udelay (100);
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04,
	devc->mt_base + 0x18);

  unload_midi (devc);

  if (devc->auxdrv->card_uninit)
     devc->auxdrv->card_uninit(devc);

  oss_unregister_interrupts (devc->osdev);

  if (devc->model_data->flags & (MF_SPDIFOUT | MF_SPDIFIN))
    {
      oss_spdif_uninstall (&devc->spdc);
    }

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

  oss_unregister_device (osdev);
  return 1;
}
