/*
 * Purpose: The High Definition Audio (HDA/Azalia) driver.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

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

#include "hdaudio_cfg.h"
#include "oss_pci.h"
#include "hdaudio.h"
#include "hdaudio_codec.h"
#include "spdif.h"

#define CORB_DELAY 10
#define CORB_LOOPS 10000

#define INTEL_VENDOR_ID         0x8086
#define INTEL_DEVICE_ICH6       0x2668
#define INTEL_DEVICE_ICH7       0x27d8
#define INTEL_DEVICE_ESB2       0x269a
#define INTEL_DEVICE_ICH8       0x284b
#define INTEL_DEVICE_ICH9       0x293f

#define NVIDIA_VENDOR_ID        0x10de
#define NVIDIA_DEVICE_MCP51     0x026c
#define NVIDIA_DEVICE_MCP55     0x0371
#define NVIDIA_DEVICE_MCP61     0x03e4
#define NVIDIA_DEVICE_MCP61A    0x03f0
#define NVIDIA_DEVICE_MCP65     0x044a
#define NVIDIA_DEVICE_MCP67     0x055c

#define ATI_VENDOR_ID           0x1002
#define ATI_DEVICE_SB450        0x437b
#define ATI_DEVICE_SB600        0x4383

#define VIA_VENDOR_ID           0x1106
#define VIA_DEVICE_HDA          0x3288

#define SIS_VENDOR_ID           0x1039
#define SIS_DEVICE_HDA          0x7502

#define ULI_VENDOR_ID           0x10b9
#define ULI_DEVICE_HDA          0x5461

#define BDL_SIZE	32
#define HDA_MAX_ENGINES	8
#define MAX_OUTPUTS 	8
#define MAX_INPUTS	4

static int nHDAdevs = 0;

typedef struct
{
  oss_uint64_t addr;
  unsigned int len;
  unsigned int ioc;
} bdl_t;

typedef struct hda_portc hda_portc;

typedef struct
{
  int num;
  int busy;
  bdl_t *bdl;
  oss_uint64_t bdl_phys;
  int bdl_size, bdl_max;
  unsigned char *base;
  unsigned int intrmask;
  hda_portc *portc;
} hda_engine_t;

struct hda_portc
{
  int num;
  int open_mode;
  int speed, bits, channels;
  int max_channels;
  int audio_enabled;
  int trigger_bits;
  int audiodev;
  int port_type;
#define PT_OUTPUT	0
#define PT_INPUT	1
  hdaudio_endpointinfo_t *endpoint;
  hda_engine_t *engine;
};

typedef struct
{
  unsigned int response, resp_ex;
} rirb_entry_t;

typedef struct hda_devc
{
  oss_device_t *osdev;
  oss_native_word base;
  unsigned int membar_addr;
  unsigned char *azbar;

  char *chip_name;

  int irq;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* CORB and RIRB */
  int corbsize, rirbsize;
  unsigned int *corb;
  rirb_entry_t *rirb;
  oss_uint64_t corb_phys, rirb_phys;
  int rirb_rp;
  unsigned int rirb_upper, rirb_lower;
  volatile int rirb_empty;

  /* Mixer */
  unsigned short codecmask;
  hdaudio_mixer_t *mixer;
  int mixer_dev;
  spdif_devc spdc;

  /* Audio */
  int first_dev;
  hda_engine_t inengines[HDA_MAX_ENGINES];
  hda_engine_t outengines[HDA_MAX_ENGINES];
  int num_outengines, num_inengines;
  hdaudio_endpointinfo_t *spdifout_endpoint;

  hda_portc output_portc[MAX_OUTPUTS];
  hda_portc input_portc[MAX_INPUTS];

  int num_spdin, num_spdout;
}
hda_devc;

static int
rirb_intr (hda_devc * devc)
{
  int serviced = 0;
  unsigned char rirbsts;
  oss_native_word flags;

  rirbsts = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBSTS);
  if (rirbsts != 0)
    {
      serviced = 1;

      if (rirbsts & 0x01)
	{
	  /* RIRB response interrupt */
	  int wp, rp;
	  unsigned int upper = 0, lower = 0;

	  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
	  wp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBWP) & 0x00ff;
	  while (devc->rirb_rp != wp)
	    {
	      devc->rirb_rp++;
	      devc->rirb_rp %= devc->rirbsize;
	      rp = devc->rirb_rp;
	      upper = devc->rirb[rp].response;
	      lower = devc->rirb[rp].resp_ex;

	      if (lower & 0x10)	/* Unsolicited response */
		{
		  // hda_codec_unsol(devc->mixer, upper, lower);
		}
	      else if (devc->rirb_empty)
		{
		  devc->rirb_upper = upper;
		  devc->rirb_lower = lower;
		  devc->rirb_empty--;
		}
	    }
	  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	}
      PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBSTS, rirbsts);
    }

  return serviced;
}

static int
hdaintr (oss_device_t * osdev)
{
  hda_devc *devc = (hda_devc *) osdev->devc;
  unsigned int status;
  int serviced = 0;
  int i;

  status = PCI_READL (devc->osdev, devc->azbar + HDA_INTSTS);
  if (status != 0)
    {
      serviced = 1;

      for (i = 0; i < devc->num_outengines; i++)
	{
	  hda_engine_t *engine = &devc->outengines[i];

	  hda_portc *portc;
	  if (status & engine->intrmask)
	    {
	      PCI_WRITEB (devc->osdev, engine->base + 0x03, 0x1e);

	      portc = engine->portc;

	      if (portc != NULL && (portc->trigger_bits & PCM_ENABLE_OUTPUT))
		oss_audio_outputintr (portc->audiodev, 1);
	    }
	}

      for (i = 0; i < devc->num_inengines; i++)
	{
	  hda_engine_t *engine = &devc->inengines[i];

	  hda_portc *portc;
	  if (status & engine->intrmask)
	    {
	      PCI_WRITEB (devc->osdev, engine->base + 0x03, 0x1e);

	      portc = engine->portc;

	      if (portc != NULL && (portc->trigger_bits & PCM_ENABLE_INPUT))
		oss_audio_inputintr (portc->audiodev, 0);
	    }
	}
      PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTSTS, status);	/* ACK */

      if (status & (1 << 30))	/* Controller interrupt (RIRB) */
	{
	  if (rirb_intr (devc))
	    serviced = 1;
	}
    }
  return serviced;
}

static int
do_corb_write (void *dc, unsigned int cad, unsigned int nid, unsigned int d,
	       unsigned int verb, unsigned int parm)
{
  unsigned int wp;
  unsigned int tmp, tmout;
  unsigned short w;
  oss_native_word flags;
  hda_devc *devc = (hda_devc *) dc;

  tmp = (cad << 28) | (d << 27) | (nid << 20) | (verb << 8) | (parm & 0xffff);
  wp = PCI_READB (devc->osdev, devc->azbar + HDA_CORBWP) & 0x00ff;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  wp = (wp + 1) % devc->corbsize;

  devc->corb[wp] = tmp;
  devc->rirb_empty++;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBWP, wp);

  return 1;
}

static int
do_corb_read (void *dc, unsigned int cad, unsigned int nid, unsigned int d,
	      unsigned int verb, unsigned int parm, unsigned int *upper,
	      unsigned int *lower)
{
  int tmout = 0;
  unsigned short w;
  hda_devc *devc = (hda_devc *) dc;
  oss_native_word flags;

  do_corb_write (devc, cad, nid, d, verb, parm);

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  tmout = CORB_LOOPS;
  while (devc->rirb_empty)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

      if (--tmout < 0)
	{
	  devc->rirb_rp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBWP);
	  devc->rirb_empty = 0;
	  return 0;
	}
      oss_udelay (CORB_DELAY);
      MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
    }

  *upper = devc->rirb_upper;
  *lower = devc->rirb_lower;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 1;
}

static int
do_corb_read_poll (void *dc, unsigned int cad, unsigned int nid,
		   unsigned int d, unsigned int verb, unsigned int parm,
		   unsigned int *upper, unsigned int *lower)
{
  int tmout = 0;
  unsigned short w;
  hda_devc *devc = (hda_devc *) dc;
  oss_native_word flags;

  do_corb_write (devc, cad, nid, d, verb, parm);

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  tmout = CORB_LOOPS;
  while (devc->rirb_empty)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

      rirb_intr (devc);

      if (--tmout < 0)
	{
	  devc->rirb_rp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBWP);
	  devc->rirb_empty = 0;
	  return 0;
	}
      oss_udelay (CORB_DELAY);
      MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
    }

  *upper = devc->rirb_upper;
  *lower = devc->rirb_lower;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 1;
}

#undef corb_read

static int
corb_read (void *dc, unsigned int cad, unsigned int nid, unsigned int d,
	   unsigned int verb, unsigned int parm, unsigned int *upper,
	   unsigned int *lower)
{
  unsigned int intrctl;
  hda_devc *devc = (hda_devc *) dc;

/*
 * Do three retries using different access methods
 */
  if (do_corb_read (dc, cad, nid, d, verb, parm, upper, lower))
    return 1;

  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x02);	/* Intr disable */

  if (do_corb_read_poll (dc, cad, nid, d, verb, parm, upper, lower))
    {
      PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTCTL, intrctl);
      return 1;
    }

  if (do_corb_read_poll (dc, cad, nid, d, verb, parm, upper, lower))
    {
      PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTCTL, intrctl);
      return 1;
    }

#if 0
  // TODO: Implement this
  if (do_corb_read_single (dc, cad, nid, d, verb, parm, upper, lower))
    {
      PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTCTL, intrctl);
      return 1;
    }
#endif

  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x03);	/* Intr re-enable */

  cmn_err (CE_WARN,
	   "hdaudio: RIRB timeout (cad=%d, nid=%d, d=%d, verb=%03x, parm=%x)\n",
	   cad, nid, d, verb, parm);

  return 0;
}

/*
 * Audio routines
 */

static int
hda_audio_set_rate (int dev, int arg)
{
  hda_portc *portc = audio_engines[dev]->portc;
  adev_p adev = audio_engines[dev];

  int best = -1, bestdiff = 10000000;
  int i;

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

  for (i = 0; i < adev->nrates; i++)
    {
      int diff = arg - adev->rates[i];
      if (diff < 0)
	diff = -diff;

      if (diff < bestdiff)
	{
	  best = i;
	  bestdiff = diff;
	}
    }

  if (best == -1)
    {
      cmn_err (CE_WARN, "No suitable rate found!\n");
      return portc->speed = 48000;	/* Some default */
    }
  portc->speed = adev->rates[best];
  return portc->speed;
}

static short
hda_audio_set_channels (int dev, short arg)
{
  hda_portc *portc = audio_engines[dev]->portc;
  hda_devc *devc = audio_engines[dev]->devc;
  adev_p adev = audio_engines[dev];
  int i, n1, n2;

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

#if 1
  if (arg < 2)
    arg = 2;
#endif

  if (arg > portc->max_channels)
    arg = portc->max_channels;

  if (arg != 1 && arg != 2 && arg != 4 && arg != 6 && arg != 8)
    {
      cmn_err (CE_NOTE, "Ignored request for %d channels\n", arg);
      return portc->channels = 2;
    }

  if (arg < adev->min_channels)
    arg = adev->min_channels;
  if (arg > adev->max_channels)
    arg = adev->max_channels;

  /*
   * Check if more output endpoints need to be allocated
   */
  n2 = (arg + 1) / 2;
  n1 = (portc->channels + 1) / 2;
  if (n1 < 1)
    n1 = 1;

  if (n2 > n1)			/* Needs more stereo pairs */
    {
      oss_native_word flags;
      unsigned int remap = hdaudio_codec_get_output_mapping (devc->mixer);

      MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

      for (i = n1; i < n2; i++)
	{
	  int ix = i;
	  if (i < 8 && remap != 0)
	    ix = (remap >> (i * 4)) & 0x0f;
	  if (portc->endpoint[ix].busy)
	    {
	      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	      return portc->channels;
	    }
	}

      for (i = n1; i < n2; i++)
	{
	  int ix = i;
	  if (i < 8 && remap != 0)
	    ix = (remap >> (i * 4)) & 0x0f;
	  portc->endpoint[ix].busy = 1;
	  portc->endpoint->borrow_count++;
	}

      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
    }
  else if (n2 < n1)		/* Some stereo pairs can be released */
    {
      unsigned int remap = hdaudio_codec_get_output_mapping (devc->mixer);
      for (i = n2; i < n1; i++)
	{
	  int ix = i;
	  if (i < 8 && remap != 0)
	    ix = (remap >> (i * 4)) & 0x0f;

	  portc->endpoint[ix].busy = 0;
	  portc->endpoint->borrow_count--;
	}
    }

  portc->channels = arg;

  return portc->channels;
}

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

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

  if (!(arg & audio_engines[dev]->oformat_mask))
    return portc->bits;
  portc->bits = arg;

  return portc->bits;
}

static int
do_corb_write_simple (void *dc, unsigned int v)
{
  unsigned int wp;
  unsigned int tmp, tmout;
  unsigned short w;
  hda_devc *devc = (hda_devc *) dc;

  wp = PCI_READB (devc->osdev, devc->azbar + HDA_CORBWP) & 0x00ff;

  wp = (wp + 1) % devc->corbsize;

  devc->corb[wp] = v;
  devc->rirb_empty++;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBWP, wp);

  return 1;
}

static int
do_corb_read_simple (void *dc, unsigned int cmd, unsigned int *v)
{
  int tmout = 0;
  hda_devc *devc = (hda_devc *) dc;

  do_corb_write_simple (dc, cmd);

  tmout = CORB_LOOPS;
  while (devc->rirb_empty)
    {
      if (--tmout < 0)
	{
	  cmn_err (CE_WARN, "hdaudio: RIRB timeout (cmd=%08x)\n", cmd);
	  devc->rirb_rp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBWP);
	  devc->rirb_empty = 0;
	  return 0;
	}
      oss_udelay (CORB_DELAY);
    }

  *v = devc->rirb_upper;

  return 1;
}

static int
hda_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  hda_devc *devc = audio_engines[dev]->devc;
  hda_portc *portc = audio_engines[dev]->portc;
  extern int hdaudio_snoopy;

/*
 * The following debugging ioctl calls may be dangerous
 * so don't enable them by default. The "snoopy" utility
 * can be used when this option is enabled.
 */
  if (hdaudio_snoopy)
    switch (cmd)
      {
      case HDA_IOCTL_WRITE:
	do_corb_write_simple (devc, *(unsigned int *) arg);
	return 0;
	break;

      case HDA_IOCTL_READ:
	if (!do_corb_read_simple
	    (devc, *(unsigned int *) arg, (unsigned int *) arg))
	  return -EIO;

	return 0;
	break;

      case HDA_IOCTL_NAME:
	return hda_codec_getname (devc->mixer, (hda_name_t *) arg);
	break;

      }
  return hdaudio_codec_audio_ioctl (devc->mixer, portc->endpoint, cmd, arg);
}

static void hda_audio_trigger (int dev, int state);

static void
hda_audio_reset (int dev)
{
  hda_audio_trigger (dev, 0);
}

static int
hda_audio_open (int dev, int mode, int openflags)
{
  hda_portc *portc = audio_engines[dev]->portc;
  hda_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  hda_engine_t *engines, *engine = NULL;
  int i, n;
  unsigned int tmp;

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

  if (portc->port_type == PT_INPUT)
    {
      engines = devc->inengines;
      n = devc->num_inengines;
    }
  else
    {
      engines = devc->outengines;
      n = devc->num_outengines;
    }

  for (i = 0; i < n && engine == NULL; i++)
    {
      if (!engines[i].busy)
	engine = &engines[i];
    }

  if (engine == NULL)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      cmn_err (CE_WARN, "hda: No free DMA engines.\nn");
      return -EBUSY;
    }

  portc->open_mode = mode;
  portc->audio_enabled &= ~mode;
  portc->endpoint->busy = 1;
  portc->endpoint->borrow_count = 1;

  portc->engine = engine;
  engine->portc = portc;
  portc->engine->busy = 1;

  portc->endpoint->stream_number = portc->endpoint->default_stream_number;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

/*
 * Reset the DMA engine and wait for the reset to complete
 */
  tmp = PCI_READL (devc->osdev, engine->base);
  PCI_WRITEL (devc->osdev, engine->base, tmp | 0x01);	/* Stream reset */

  for (i = 0; i < 1000; i++)
    {
      if (PCI_READL (devc->osdev, engine->base) & 0x01)
	break;
      oss_udelay (1000);
    }

  /* Unreset and wait */

  tmp = PCI_READL (devc->osdev, engine->base);
  PCI_WRITEL (devc->osdev, engine->base, tmp & ~0x01);	/* Release reset */

  for (i = 0; i < 1000; i++)
    {
      if (!(PCI_READL (devc->osdev, engine->base) & 0x01))
	break;
      oss_udelay (1000);
    }

  return 0;
}

static void
hda_audio_close (int dev, int mode)
{
  hda_portc *portc = audio_engines[dev]->portc;
  hda_devc *devc = audio_engines[dev]->devc;
  unsigned int remap = hdaudio_codec_get_output_mapping (devc->mixer);
  oss_native_word flags;
  int i;

  if (!portc->open_mode)
    {
      cmn_err (CE_WARN, "Bad close %d\n", dev);
      return;
    }

  hda_audio_reset (dev);
  hdaudio_codec_reset_endpoint (devc->mixer, portc->endpoint,
				portc->channels);

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->audio_enabled &= ~mode;
  portc->engine->busy = 0;
  portc->engine->portc = NULL;
  portc->engine = NULL;
  portc->endpoint->busy = 0;
  portc->endpoint->stream_number = portc->endpoint->default_stream_number;

  for (i = 1; i < portc->endpoint->borrow_count; i++)
    {
      int ix = i;
      if (i < 8 && remap != 0)
	ix = (remap >> (i * 4)) & 0x0f;
      portc->endpoint[ix].busy = 0;
    }

  portc->endpoint->borrow_count = 1;
  portc->open_mode = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

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

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

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

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

static void
hda_audio_trigger (int dev, int state)
{
  hda_devc *devc = audio_engines[dev]->devc;
  hda_portc *portc = audio_engines[dev]->portc;
  hda_engine_t *engine = portc->engine;
  oss_native_word flags;
  unsigned char tmp, intr;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  tmp = PCI_READB (devc->osdev, engine->base + 0x00);
  intr = PCI_READB (devc->osdev, devc->azbar + HDA_INTCTL);
  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	      tmp |= 0x1e;	/* DMA and Stream Interrupt enable */
	      intr |= engine->intrmask;
	      PCI_WRITEB (devc->osdev, engine->base + 0x00, tmp);
	      PCI_WRITEB (devc->osdev, devc->azbar + HDA_INTCTL, intr);
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
	      tmp &= ~0x1e;	/* Run-off & intr disable */
	      intr &= ~engine->intrmask;
	      /* Stop DMA and Stream Int */
	      PCI_WRITEB (devc->osdev, engine->base + 0x00, tmp);
	      /* Ack pending ints */
	      PCI_WRITEB (devc->osdev, engine->base + 0x03, 0x1c);
	      /* Disable Interrupts */
	      PCI_WRITEB (devc->osdev, devc->azbar + HDA_INTCTL, intr);
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	      tmp |= 0x1e;	/* dma & intr enable */
	      intr |= engine->intrmask;
	      PCI_WRITEB (devc->osdev, engine->base + 0x00, tmp);
	      PCI_WRITEB (devc->osdev, devc->azbar + HDA_INTCTL, intr);
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_INPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_INPUT;
	      tmp &= ~0x1e;	/* Run-off & intr disable */
	      intr &= ~engine->intrmask;
	      /* Stop DMA and Stream Int */
	      PCI_WRITEB (devc->osdev, engine->base + 0x00, tmp);
	      /* Ack pending ints */
	      PCI_WRITEB (devc->osdev, engine->base + 0x03, 0x1c);
	      /* Disable Interrupts */
	      PCI_WRITEB (devc->osdev, devc->azbar + HDA_INTCTL, intr);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static void
init_bdl (hda_devc * devc, hda_engine_t * engine, dmap_p dmap)
{
  int i;
  bdl_t *bdl = engine->bdl;

  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_BDLPL, 0);
  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_BDLPU, 0);
  for (i = 0; i < dmap->nfrags; i++)
    {
      bdl[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);
      bdl[i].len = dmap->fragment_size;
      bdl[i].ioc = 0x01;
    }
}

static int
setup_audio_engine (hda_devc * devc, hda_engine_t * engine, hda_portc * portc,
		    dmap_t * dmap)
{
  unsigned int tmp, tmout;

/* make sure the run bit is zero for SD */
  PCI_WRITEB (devc->osdev, engine->base + HDA_SD_CTL,
	      PCI_READB (devc->osdev, engine->base + HDA_SD_CTL) & ~0x2);
/*
 * First reset the engine.
 */

  tmp = PCI_READB (devc->osdev, engine->base + HDA_SD_CTL);
  PCI_WRITEB (devc->osdev, engine->base + HDA_SD_CTL, tmp | 0x01);	/* Reset */
  oss_udelay (1000);

  tmout = 300;
  while (!(PCI_READB (devc->osdev, engine->base + HDA_SD_CTL) & 0x01)
	 && --tmout)
    oss_udelay (1000);

  PCI_WRITEB (devc->osdev, engine->base + HDA_SD_CTL, tmp & ~0x01);	/* Release reset */
  oss_udelay (1000);
  tmout = 300;
  while ((PCI_READB (devc->osdev, engine->base + HDA_SD_CTL) & 0x01)
	 && --tmout)
    oss_udelay (1000);

/*
 * Set the engine tag number field
 */
  tmp = PCI_READL (devc->osdev, engine->base + HDA_SD_CTL);
  tmp &= ~(0xf << 20);
  tmp |= portc->endpoint->stream_number << 20;
  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_CTL, tmp);

/* program buffer size and fragments in the engine */

  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_CBL, dmap->bytes_in_use);
  PCI_WRITEW (devc->osdev, engine->base + HDA_SD_LVI, dmap->nfrags - 1);

/* setup the BDL base address */
  tmp = engine->bdl_phys;
  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_BDLPL, tmp & 0xffffffff);
  tmp = engine->bdl_phys >> 32;
  PCI_WRITEL (devc->osdev, engine->base + HDA_SD_BDLPU, tmp & 0xffffffff);
  PCI_WRITEB (devc->osdev, engine->base + HDA_SD_STS, 0x1c);	/* mask out ints */

/*
 * Next the sample rate and format setup
 */
  if (hdaudio_codec_setup_endpoint
      (devc->mixer, portc->endpoint, portc->speed, portc->channels,
       portc->bits, portc->endpoint->stream_number, &tmp) < 0)
    {
      cmn_err (CE_WARN, "Codec setup failed\n");
      return -EIO;
    }

  PCI_WRITEW (devc->osdev, engine->base + HDA_SD_FORMAT, tmp);
  tmp = PCI_READW (devc->osdev, engine->base + HDA_SD_FORMAT);

  return 0;
}

static int
hda_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  hda_devc *devc = audio_engines[dev]->devc;
  hda_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  oss_native_word flags;
  hda_engine_t *engine;

  if (portc->port_type != PT_INPUT)
    return -ENOTSUP;

  engine = portc->engine;

  if (dmap->nfrags > engine->bdl_max)	/* Overflow protection */
    {
      dmap->nfrags = engine->bdl_max;
      dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size;
    }

  init_bdl (devc, engine, dmap);

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return setup_audio_engine (devc, engine, portc, dmap);
}

static int
hda_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  hda_devc *devc = audio_engines[dev]->devc;
  hda_portc *portc = audio_engines[dev]->portc;
  hda_engine_t *engine;

  dmap_t *dmap = audio_engines[dev]->dmap_out;
  oss_native_word flags;

  if (portc->port_type != PT_OUTPUT)
    return -ENOTSUP;

  engine = portc->engine;

  if (dmap->nfrags > engine->bdl_max)	/* Overflow protection */
    {
      dmap->nfrags = engine->bdl_max;
      dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size;
    }

  init_bdl (devc, engine, dmap);

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

  return setup_audio_engine (devc, engine, portc, dmap);
}

static int
hda_check_input (int dev)
{
  cmn_err (CE_WARN, "Input timed out on %s.\n", audio_engines[dev]->devnode);
  return -EIO;
}

static int
hda_check_output (int dev)
{
#if 0
  hda_portc *portc = audio_engines[dev]->portc;

  cmn_err (CE_WARN, "Ptr %08x, sts %08x\n",
	   PCI_READL (NULL, portc->engine->base + 0x04), PCI_READB (NULL,
								    portc->
								    engine->
								    base +
								    0x03));
#endif
  cmn_err (CE_WARN, "Output timed out on %s.\n", audio_engines[dev]->devnode);
  return -EIO;
}

static int
hda_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  hda_devc *devc = audio_engines[dev]->devc;
  hda_portc *portc = audio_engines[dev]->portc;

  unsigned int ptr;

  ptr =
    PCI_READL (devc->osdev,
	       portc->engine->base + HDA_SD_LPIB) % dmap->bytes_in_use;

  return ptr;
}

static const audiodrv_t hda_audio_driver = {
  hda_audio_open,
  hda_audio_close,
  hda_audio_output_block,
  hda_audio_start_input,
  hda_audio_ioctl,
  hda_audio_prepare_for_input,
  hda_audio_prepare_for_output,
  hda_audio_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  hda_audio_trigger,
  hda_audio_set_rate,
  hda_audio_set_format,
  hda_audio_set_channels,
  NULL,
  NULL,
  hda_check_input,
  hda_check_output,
  NULL,				/* hda_alloc_buffer */
  NULL,				/* hda_free_buffer */
  NULL,
  NULL,
  hda_get_buffer_pointer
};

static int
reset_controller (hda_devc * devc)
{
  unsigned int tmp, tmout;

  tmp = PCI_READL (devc->osdev, devc->azbar + HDA_GCTL);
  tmp &= ~CRST;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_GCTL, tmp);

  tmout = 50;
  while (!(PCI_READL (devc->osdev, devc->azbar + HDA_GCTL)) && --tmout)
    oss_udelay (1000);

  oss_udelay (1000);

  tmp = PCI_READL (devc->osdev, devc->azbar + HDA_GCTL);
  tmp |= CRST;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_GCTL, tmp);

  tmout = 50;
  while (!(PCI_READL (devc->osdev, devc->azbar + HDA_GCTL)) && --tmout)
    oss_udelay (1000);

  oss_udelay (1000);

  if (!(PCI_READL (devc->osdev, devc->azbar + HDA_GCTL)))
    {
      cmn_err (CE_WARN, "hdaudio: controller not ready\n");
      return 0;
    }

  if (!devc->codecmask)
    {
      devc->codecmask = PCI_READL (devc->osdev, devc->azbar + HDA_STATESTS);
      DDB (cmn_err (CE_CONT, "Codec mask %x\n", devc->codecmask));
    }

  return 1;
}

static int
setup_controller (hda_devc * devc)
{
  unsigned int tmp, tmout;
  oss_native_word phaddr;

/*
 * Allocate the CORB and RIRB buffers.
 */
  tmp = PCI_READB (devc->osdev, devc->azbar + HDA_CORBSIZE) & 0x03;

  switch (tmp)
    {
    case 0:
      devc->corbsize = 2;
      break;
    case 1:
      devc->corbsize = 16;
      break;
    case 2:
      devc->corbsize = 256;
      break;
    default:
      cmn_err (CE_WARN, "hdaudio: Bad CORB size\n");
      return 0;
    }

  tmp = PCI_READB (devc->osdev, devc->azbar + HDA_RIRBSIZE) & 0x03;

  switch (tmp)
    {
    case 0:
      devc->rirbsize = 2;
      break;
    case 1:
      devc->rirbsize = 16;
      break;
    case 2:
      devc->rirbsize = 256;
      break;
    default:
      cmn_err (CE_WARN, "hdaudio: Bad CORB size\n");
      return 0;
    }

  if ((devc->corb =
       CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, &phaddr)) == NULL)
    {
      cmn_err (CE_WARN, "hdaudio: Out of memory (CORB)\n");
      return 0;
    }

  devc->corb_phys = phaddr;

  devc->rirb = (rirb_entry_t *) (devc->corb + 512);	/* 512 dwords = 2048 bytes */
  devc->rirb_phys = devc->corb_phys + 2048;

/*
 * Initialize CORB registers
 */

  PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBCTL, 0);	/* Stop */

  tmout = 0;

  while (tmout++ < 500
	 && (PCI_READB (devc->osdev, devc->azbar + HDA_CORBCTL) & 0x02));

  if (PCI_READB (devc->osdev, devc->azbar + HDA_CORBCTL) & 0x02)
    {
      cmn_err (CE_WARN, "hdaudio: CORB didn't stop\n");
    }

  tmp = devc->corb_phys & 0xffffffff;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBLBASE, tmp);
  tmp = (devc->corb_phys >> 32) & 0xffffffff;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_CORBUBASE, tmp);

  PCI_WRITEW (devc->osdev, devc->azbar + HDA_CORBWP, 0x0);	/* Reset to 0 */
  PCI_WRITEW (devc->osdev, devc->azbar + HDA_CORBRP, 0x8000);	/* Reset to 0 */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBCTL, 0x02);	/* Start */

/*
 * Initialize RIRB registers
 */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0);	/* Stop */

  tmout = 0;

  while (tmout++ < 500
	 && (PCI_READB (devc->osdev, devc->azbar + HDA_RIRBCTL) & 0x02));

  if (PCI_READB (devc->osdev, devc->azbar + HDA_RIRBCTL) & 0x02)
    {
      cmn_err (CE_WARN, "hdaudio: RIRB didn't stop\n");
    }

  tmp = devc->rirb_phys & 0xffffffff;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_RIRBLBASE, tmp);
  tmp = (devc->rirb_phys >> 32) & 0xffffffff;
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_RIRBUBASE, tmp);
  devc->rirb_rp = 0;

  PCI_WRITEW (devc->osdev, devc->azbar + HDA_RIRBWP, 0x8000);	/* Reset to 0 */
  PCI_WRITEW (devc->osdev, devc->azbar + HDA_RINTCNT, 1);	/* reset hw write ptr */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0x03);	/* Start & Intr enable */

  return 1;
}

static int
setup_engines (hda_devc * devc)
{
  int i, p;
  unsigned int gcap;
  unsigned int num_inputs, num_outputs;
  oss_native_word phaddr;
  gcap = PCI_READW (devc->osdev, devc->azbar + HDA_GCAP);

  num_outputs = (gcap >> 12) & 0x0f;
  num_inputs = (gcap >> 8) & 0x0f;

  /* Init output engines */
  p = num_inputs;		/* Output engines are located after the input ones */

  if (num_outputs > HDA_MAX_ENGINES)
    {
      cmn_err (CE_WARN,
	       "hdaudio: Using only %d out of %d available output engines\n",
	       HDA_MAX_ENGINES, num_outputs);
      num_outputs = HDA_MAX_ENGINES;
    }

  if (num_inputs > HDA_MAX_ENGINES)
    {
      cmn_err (CE_WARN,
	       "hdaudio: Using only %d out of %d available input engines\n",
	       HDA_MAX_ENGINES, num_inputs);
      num_inputs = HDA_MAX_ENGINES;
    }

  for (i = 0; i < num_outputs; i++)
    {
      hda_engine_t *engine = &devc->outengines[i];

      engine->bdl =
	CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, &phaddr);
      if (engine->bdl == NULL)
	{
	  cmn_err (CE_WARN, "hdaudio: Out of memory\n");
	  return 0;
	}
      engine->bdl_max = 4096 / sizeof (bdl_t);
      engine->bdl_phys = phaddr;
      engine->base = devc->azbar + 0x80 + (p * 0x20);
      engine->num = i;
      engine->intrmask = (1 << p);
      engine->portc = NULL;
      p++;
      devc->num_outengines = i + 1;
    }

  /* Init input engines */
  p = 0;
  for (i = 0; i < num_inputs; i++)
    {
      hda_engine_t *engine = &devc->inengines[i];

      engine->bdl =
	CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, &phaddr);
      if (engine->bdl == NULL)
	{
	  cmn_err (CE_WARN, "hdaudio: Out of memory\n");
	  return 0;
	}
      engine->bdl_max = 4096 / sizeof (bdl_t);
      engine->bdl_phys = phaddr;
      engine->base = devc->azbar + 0x80 + (p * 0x20);
      engine->num = i;
      engine->intrmask = (1 << p);
      engine->portc = NULL;
      p++;
      devc->num_inengines = i + 1;
    }
  return 1;
}

static void
init_adev_caps (hda_devc * devc, adev_p adev, hdaudio_endpointinfo_t * ep)
{
  int i;

  adev->oformat_mask = ep->fmt_mask;
  adev->iformat_mask = ep->fmt_mask;

  adev->min_rate = 500000;
  adev->max_rate = 0;

  adev->nrates = ep->nrates;

  for (i = 0; i < ep->nrates; i++)
    {
      int r = ep->rates[i];

      adev->rates[i] = r;

      if (adev->min_rate > r)
	adev->min_rate = r;
      if (adev->max_rate < r)
	adev->max_rate = r;
    }
}

/*
 * S/PDIF lowlevel driver
 */
static int
hdaudio_reprogram_spdif (void *_devc, void *_portc,
			 oss_digital_control * ctl, unsigned int mask)
{
  unsigned short cbits = 0;
  hda_devc *devc = _devc;
  oss_native_word flags;
  hdaudio_endpointinfo_t *endpoint = devc->spdifout_endpoint;

  if (endpoint == NULL)
    return -EIO;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  cbits = 0x01;			/* Digital interface enabled */

  cbits |= (1 << 2);		/* VCFG */

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

  if (ctl->cbitout[0] & 0x02)	/* Audio/Data */
    cbits |= (1 << 5);		/* /AUDIO */

  if (ctl->cbitout[0] & 0x01)	/* Pro */
    cbits |= (1 << 6);
  else
    {

      if (ctl->cbitout[0] & 0x04)	/* Copyright */
	cbits |= (1 << 4);	/* COPY */

      if (ctl->cbitout[1] & 0x80)	/* Generation level */
	cbits |= (1 << 7);	/* L */

      if (ctl->emphasis_type & 1)	/* Pre-emphasis */
	cbits |= (1 << 3);	/* PRE */
    }
  do_corb_write (devc, endpoint->cad, endpoint->wid, 0, SET_SPDIF_CONTROL1,
		 cbits);

  cbits = ctl->cbitout[1] & 0x7f;	/* Category code */
  do_corb_write (devc, endpoint->cad, endpoint->wid, 0, SET_SPDIF_CONTROL2,
		 cbits);

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

spdif_driver_t hdaudio_spdif_driver = {
/* reprogram_device: */ hdaudio_reprogram_spdif,
};

static int
spdif_mixer_init (int dev)
{
  hda_devc *devc = mixer_devs[dev]->hw_devc;
  int err;

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

  return 0;
}

static void
install_outputdevs (hda_devc * devc)
{
  int i, n, audio_dev;
  char tmp_name[64];
  hdaudio_endpointinfo_t *endpoints;

  unsigned int remap = hdaudio_codec_get_output_mapping (devc->mixer);

  if ((n =
       hdaudio_mixer_get_outendpoints (devc->mixer, &endpoints,
				       sizeof (*endpoints))) < 0)
    return;

  if (n > MAX_OUTPUTS)
    {
      cmn_err (CE_WARN,
	       "hda: Only %d out of %d output devices can be installed\n",
	       MAX_OUTPUTS, n);
      n = MAX_OUTPUTS;
    }

  for (i = 0; i < n; i++)
    {
      adev_p adev;
      hda_portc *portc = &devc->output_portc[i];
      unsigned int formats = AFMT_S16_LE;
      int opts = ADEV_AUTOMODE | ADEV_NOINPUT;
      int ix = i;

      if (i < 8 && remap != 0)
	ix = (remap >> (i * 4)) & 0x0f;

      if (i == 0)
	portc->max_channels = 8;
      else
	portc->max_channels = 2;

      if (endpoints[ix].is_digital)
	{
	  char devname[16];
	  opts |= ADEV_SPECIAL;
	  sprintf (devname, "spdout%d", devc->num_spdout++);
	  oss_audio_set_devname (devname);
	}

      sprintf (tmp_name, "%s %s output", devc->chip_name, endpoints[ix].name);

      if ((audio_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					     devc->osdev,
					     devc->osdev,
					     tmp_name,
					     &hda_audio_driver,
					     sizeof (audiodrv_t),
					     opts, formats, NULL, -1)) < 0)
	{
	  return;
	}

      if (i == 0)
	devc->first_dev = audio_dev;

      adev = audio_engines[audio_dev];

      adev->devc = devc;
      adev->portc = portc;
      adev->rate_source = devc->first_dev;
      adev->mixer_dev = devc->mixer_dev;
      adev->min_rate = 8000;
      adev->max_rate = 192000;
      adev->min_channels = 2;
      adev->max_channels = portc->max_channels;
      if (portc->max_channels > 4)
	adev->dmabuf_alloc_flags |= DMABUF_LARGE;
      adev->min_block = 128;	/* Hardware limitation */
      adev->min_fragments = 4;	/* Vmix doesn't work without this */
      portc->num = i;
      portc->open_mode = 0;
      portc->audio_enabled = 0;
      portc->audiodev = audio_dev;
      portc->port_type = PT_OUTPUT;
      portc->endpoint = &endpoints[ix];
      init_adev_caps (devc, adev, portc->endpoint);
      portc->engine = NULL;

      if (portc->endpoint->is_digital)
	{
	  int err;

	  devc->spdifout_endpoint = portc->endpoint;

	  if ((err = oss_spdif_install (&devc->spdc, devc->osdev,
					&hdaudio_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_WARN,
		       "hdaudio: S/PDIF driver install failed. error %d\n",
		       err);
	    }
	  else
	    {
	      hdaudio_mixer_set_initfunc (devc->mixer, spdif_mixer_init);
	    }
	}
    }
}

static void
install_inputdevs (hda_devc * devc)
{
  int i, n, audio_dev;
  char tmp_name[64];
  hdaudio_endpointinfo_t *endpoints;

  if ((n =
       hdaudio_mixer_get_inendpoints (devc->mixer, &endpoints,
				      sizeof (*endpoints))) < 0)
    return;

  if (n > MAX_INPUTS)
    {
      cmn_err (CE_WARN,
	       "hda: Only %d out of %d input devices can be installed\n",
	       MAX_INPUTS, n);
      n = MAX_INPUTS;
    }

  for (i = 0; i < n; i++)
    {
      adev_p adev;
      hda_portc *portc = &devc->input_portc[i];
      unsigned int formats = AFMT_S16_LE;
      int opts = ADEV_AUTOMODE | ADEV_NOOUTPUT;

      if (endpoints[i].is_digital)
	{
	  char devname[16];
	  opts |= ADEV_SPECIAL;
	  sprintf (devname, "spdin%d", devc->num_spdin++);
	  oss_audio_set_devname (devname);
	}
      portc->max_channels = 2;

      sprintf (tmp_name, "%s %s input", devc->chip_name, endpoints[i].name);

      if ((audio_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					     devc->osdev,
					     devc->osdev,
					     tmp_name,
					     &hda_audio_driver,
					     sizeof (audiodrv_t),
					     opts, formats, NULL, -1)) < 0)
	{
	  return;
	}

      if (i == 0 && devc->first_dev == -1)
	devc->first_dev = audio_dev;

      adev = audio_engines[audio_dev];

      adev->devc = devc;
      adev->portc = portc;
      adev->rate_source = devc->first_dev;
      adev->mixer_dev = devc->mixer_dev;
      adev->min_rate = 8000;
      adev->max_rate = 192000;
      adev->min_channels = 2;
      adev->max_channels = 2;
      adev->min_block = 128;	/* Hardware limitation */
      adev->min_fragments = 4;	/* Vmix doesn't work without this */
      portc->num = i;
      portc->open_mode = 0;
      portc->audio_enabled = 0;
      portc->audiodev = audio_dev;
      portc->port_type = PT_INPUT;
      portc->endpoint = &endpoints[i];
      portc->engine = NULL;
      init_adev_caps (devc, adev, portc->endpoint);
    }
}

static int
init_HDA (hda_devc * devc)
{
  unsigned int gcap;
  unsigned int tmp;

  /* Reset controller */

  if (!reset_controller (devc))
    return 0;

  PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTCTL, PCI_READL (devc->osdev, devc->azbar + HDA_INTCTL) | 0xc0000000);	/* Intr enable */

  /*
   * Set CORB and RIRB sizes to 256, 16 or 2 entries. 
   *
   */
  tmp = (PCI_READB (devc->osdev, devc->azbar + HDA_CORBSIZE)>>4) & 0x07;
  if (tmp & 0x4) /* 256 entries */
     PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBSIZE, 0x2);
  else
  if (tmp & 0x2) /* 16 entries */
     PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBSIZE, 0x1);
  else /* Assume that 2 entries is supported */
     PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBSIZE, 0x0);

  tmp = (PCI_READB (devc->osdev, devc->azbar + HDA_RIRBSIZE)>>4) & 0x07;
  if (tmp & 0x4) /* 256 entries */
     PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBSIZE, 0x2);
  else
  if (tmp & 0x2) /* 16 entries */
     PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBSIZE, 0x1);
  else /* Assume that 2 entries is supported */
     PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBSIZE, 0x0);

  /* setup the CORB/RIRB structs */
  if (!setup_controller (devc))
    return 0;

  /* setup the engine structs */
  if (!setup_engines (devc))
    return 0;

  if (!devc->codecmask)
    {
      cmn_err (CE_WARN, "HDA: No codecs found after reset\n");
      return 0;
    }
  else
    devc->mixer = hdaudio_mixer_create (devc->chip_name, devc, devc->osdev,
					do_corb_write, corb_read,
					devc->codecmask);
  if (devc->mixer == NULL)
    return 0;

  devc->mixer_dev = hdaudio_mixer_get_mixdev (devc->mixer);

  gcap = PCI_READW (devc->osdev, devc->azbar + HDA_GCAP);

  if (((gcap >> 3) & 0x0f) > 0)
    cmn_err (CE_WARN,
	     "hdaudio: Warning! Bidirectional engines not supported\n");
  if (((gcap >> 1) & 0x03) > 0)
    cmn_err (CE_WARN, "hdaudio: Warning! Multiple SDOs not supported\n");

  install_outputdevs (devc);
  install_inputdevs (devc);

  return 1;
}


int
hdaudio_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision, btmp;
  unsigned short pci_command, vendor, device, wtmp;
  hda_devc *devc;
  static int already_attached = 0;
  int err;

  DDB (cmn_err (CE_CONT, "hdaudio_attach entered\n"));

  if (already_attached)
    {
      cmn_err (CE_WARN, "hdaudio_attach: Already attached\n");
      return 0;
    }
  already_attached = 1;

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

  if ((vendor != INTEL_VENDOR_ID && vendor != NVIDIA_VENDOR_ID &&
       vendor != ATI_VENDOR_ID && vendor != SIS_VENDOR_ID &&
       vendor != VIA_VENDOR_ID && vendor != ULI_VENDOR_ID) ||
      (device != INTEL_DEVICE_ICH6 && device != INTEL_DEVICE_ICH7 &&
       device != INTEL_DEVICE_ESB2 && device != INTEL_DEVICE_ICH8 &&
       device != INTEL_DEVICE_ICH9 && device != NVIDIA_DEVICE_MCP51 &&
       device != NVIDIA_DEVICE_MCP55 && device != NVIDIA_DEVICE_MCP61 &&
       device != NVIDIA_DEVICE_MCP61A && device != NVIDIA_DEVICE_MCP65 &&
       device != NVIDIA_DEVICE_MCP67 && device != VIA_DEVICE_HDA &&
       device != SIS_DEVICE_HDA && device != ULI_DEVICE_HDA &&
       device != ATI_DEVICE_SB450 && device != ATI_DEVICE_SB600))

    {

      return 0;
    }

  pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision);
  pci_read_config_word (osdev, PCI_COMMAND, &pci_command);
  pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line);

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

  devc->osdev = osdev;
  osdev->devc = devc;
  devc->first_dev = -1;

  oss_pci_byteswap (osdev, 1);

  switch (device)
    {
    case INTEL_DEVICE_ICH6:
    case INTEL_DEVICE_ICH7:
    case INTEL_DEVICE_ESB2:
    case INTEL_DEVICE_ICH8:
    case INTEL_DEVICE_ICH9:
      devc->chip_name = "Intel HD Audio";
      break;

    case NVIDIA_DEVICE_MCP51:
    case NVIDIA_DEVICE_MCP55:
    case NVIDIA_DEVICE_MCP61:
    case NVIDIA_DEVICE_MCP61A:
    case NVIDIA_DEVICE_MCP65:
    case NVIDIA_DEVICE_MCP67:
      devc->chip_name = "nVidia HD Audio";
      pci_read_config_byte (osdev, 0x4e, &btmp);
      pci_write_config_byte (osdev, 0x4e, (btmp & 0xf0) | 0x0f);
      break;

    case ATI_DEVICE_SB450:
    case ATI_DEVICE_SB600:
      devc->chip_name = "ATI HD Audio";
      pci_read_config_byte (osdev, 0x42, &btmp);
      pci_write_config_byte (osdev, 0x42, (btmp & 0xf8) | 0x2);
      break;

    case VIA_DEVICE_HDA:
      devc->chip_name = "VIA HD Audio";
      break;

    case SIS_DEVICE_HDA:
      devc->chip_name = "SiS HD Audio";
      break;

    case ULI_DEVICE_HDA:
      devc->chip_name = "ULI HD Audio";
      pci_read_config_word (osdev, 0x40, &wtmp);
      pci_write_config_word (osdev, 0x40, wtmp | 0x10);
      pci_write_config_dword (osdev, PCI_MEM_BASE_ADDRESS_1, 0);
      break;
    }

  pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &devc->membar_addr);

  devc->membar_addr &= ~7;

  /* get virtual address */
  devc->azbar =
    (void *) MAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, 16 * 1024);

  /* activate the device */
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  if (pci_irq_line == 0)
    {
      cmn_err (CE_WARN, "Undefined IRQ.\n");
      return 0;
    }
  devc->irq = pci_irq_line;

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

  oss_register_device (osdev, devc->chip_name);

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

  devc->base = devc->membar_addr;

  /* setup the TCSEL register */
  pci_read_config_byte (osdev, 0x44, &btmp);
  pci_write_config_byte (osdev, 0x44, btmp & 0xf8);

  err = init_HDA (devc);
  return err;
}

int
hdaudio_detach (oss_device_t * osdev)
{
  hda_devc *devc = (hda_devc *) osdev->devc;
  int i, j;

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

  PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTSTS, 0xc0000000);	/* ack pending ints */

  PCI_WRITEL (devc->osdev, devc->azbar + HDA_INTCTL, 0);	/* Intr disable */
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_STATESTS, 0x7);	/* Intr disable */
  PCI_WRITEL (devc->osdev, devc->azbar + HDA_RIRBSTS, 0x5);	/* Intr disable */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_RIRBCTL, 0);	/* Stop */
  PCI_WRITEB (devc->osdev, devc->azbar + HDA_CORBCTL, 0);	/* Stop */

  oss_unregister_interrupts (devc->osdev);

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

  if (devc->corb != NULL)
    CONTIG_FREE (devc->osdev, devc->corb, 4096);

  devc->corb = NULL;

  for (j = 0; j < devc->num_outengines; j++)
    {
      hda_engine_t *engine = &devc->outengines[j];

      if (engine->bdl == NULL)
	continue;
      CONTIG_FREE (devc->osdev, engine->bdl, 4096);
    }

  for (j = 0; j < devc->num_inengines; j++)
    {
      hda_engine_t *engine = &devc->inengines[j];

      if (engine->bdl == NULL)
	continue;
      CONTIG_FREE (devc->osdev, engine->bdl, 4096);
    }

  if (devc->membar_addr != 0)
    {
      UNMAP_PCI_MEM (devc->osdev, devc->membar_addr, devc->azbar, 16 * 1024);
      devc->membar_addr = 0;
    }

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