Google
 

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

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

oss_envy24ht/oss_envy24ht.c

VIA ENVY24HT chipset driver.

Description




This file is part of Open Sound System.

Copyright (C) 4Front Technologies 1996-2008.

This this source file is released under GPL v2 license (no other versions). See the COPYING file included in the main directory of this source distribution for the license terms and conditions.


#include "oss_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;
}

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

Back to index OSS web site


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