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


#include "atiaudio_cfg.h"
#include "oss_pci.h"
#include "ac97.h"

#define ATI_VENDOR_ID           0x1002
#define ATI_DEVICE_IXP         	0x4341
#define ATI_DEVICE_IXP300       0x4361
#define ATI_DEVICE_IXP400       0x4370

#define MAX_PORTC 3
#define BDL_SIZE	64

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

typedef struct
{
  unsigned int addr;
  unsigned short status;
  unsigned short size;
  unsigned int next;
}
bdl_t;

typedef struct
{
  oss_device_t *osdev;
  oss_native_word membar_addr;
  char *membar_virt;
  int irq;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* Mixer */
  ac97_devc ac97devc;
  int mixer_dev;

  /* Audio parameters */
  int open_mode;

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

  int play_currbuf, play_currfrag;
  int rec_currbuf, rec_currfrag;
  char *chip_name;
  ATI_portc portc[MAX_PORTC];
  int play_frag_index[BDL_SIZE];
  int rec_frag_index[BDL_SIZE];
}
ATI_devc;


/* Mem mapped I/O registers */
#define READL(o,a)       *(volatile unsigned int*)(devc->membar_virt+(a))
#define READW(o,a)       *(volatile unsigned short*)(devc->membar_virt+(a))
#define READB(o,a)       *(volatile unsigned char*)(devc->membar_virt+(a))
#define WRITEL(o,d,a)    *(volatile unsigned int*)(devc->membar_virt+(a))=d
#define WRITEW(o,d,a)    *(volatile unsigned short*)(devc->membar_virt+(a))=d
#define WRITEB(o,d,a)    *(volatile unsigned char*)(devc->membar_virt+(a))=d


static int
ac97_ready (void *devc_)
{
  ATI_devc *devc = devc_;
  int timeout = 10000;

  while (READL (devc->osdev, 0x0c) & 0x100)
    {
      if (!timeout--)
	{
	  cmn_err (CE_WARN, "codec ready timed out\n");
	  return -EIO;
	}
      oss_udelay (10);
    }
  return 0;
}

static int
ac97_read (void *devc_, int reg)
{
  ATI_devc *devc = devc_;
  unsigned int data = 0;
  unsigned int addr = 0;
  int timeout;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

again:
  if (ac97_ready (devc) < 0)
    {
      cmn_err (CE_WARN, "ac97 not ready\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return -EIO;
    }

  addr = (unsigned int) (reg << 9) | (1 << 8) | (1 << 2);
  WRITEL (devc->osdev, addr, 0x0c);	/* Set read commmand */

  if (ac97_ready (devc) < 0)
    {
      cmn_err (CE_WARN, "ac97 not ready\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return -EIO;
    }

  timeout = 1000;

  for (;;)
    {
      data = READL (devc->osdev, 0x10);	/* ac97 data port */
      if (data & 0x100)
	{
	  /* if the register returned isn't the reg sent then resend command */
	  if ((data & 0xFE00) != (reg << 9))
	    goto again;
	  else
	    break;
	}

      if (!--timeout)
	{
	  cmn_err (CE_WARN, "AC97 read timed out\n");
	  break;
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return (data >> 16);
}


static int
ac97_write (void *devc_, int reg, int data)
{
  ATI_devc *devc = devc_;
  oss_native_word flags;
  unsigned int val;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (ac97_ready (devc) < 0)
    {
      cmn_err (CE_WARN, "AC97 not ready\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }

  val = (data << 16) | (reg << 9) | 0x100;
  WRITEL (devc->osdev, val, 0x0c);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 1;
}

static int
ATIIXPintr (oss_device_t * osdev)
{
  int status, i;
  int serviced = 0;
  ATI_devc *devc = (ATI_devc *) osdev->devc;
  ATI_portc *portc;
  /* oss_native_word flags; */

/*
 * TODO: Enable the mutexes and move inputintr/outputintr calls outside the
 * mutex region.
 */

  /* MUTEX_ENTER (devc->mutex, flags); */
  /* Handle Global Interrupts */
  status = READL (devc->osdev, 0x00);

  /* handle PCM output */
  if (status & 0x08)
    for (i = 0; i < MAX_PORTC; i++)
      {
	portc = &devc->portc[i];
	if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    && (portc->port_type == DF_ANALOG))
	  {
	    dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
	    int ptr, n = 0;
	    int timeout = 1000;
	    serviced = 1;
	    while (timeout--)
	      {
		ptr = READL (devc->osdev, 0x44);

		if (ptr < dmap->dmabuf_phys)
		  continue;

		ptr -= dmap->dmabuf_phys;

		if (ptr >= dmap->bytes_in_use)
		  continue;

		break;
	      }

	    ptr /= dmap->fragment_size;

	    if ((ptr < 0) || (ptr > dmap->nfrags))
	      ptr = 0;

	    n = 0;
	    while ((dmap_get_qhead (dmap) != ptr) && (n++ < dmap->nfrags))
	      oss_audio_outputintr (portc->audiodev, 1);
	  }
      }

  /* handle PCM input */
  if (status & 0x02)
    for (i = 0; i < MAX_PORTC; i++)
      {
	portc = &devc->portc[i];
	if ((portc->trigger_bits & PCM_ENABLE_INPUT)
	    && (portc->port_type == DF_ANALOG))
	  {
	    dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in;
	    int ptr, n = 0;
	    int timeout = 1000;
	    serviced = 1;

	    while (timeout--)
	      {
		ptr = READL (devc->osdev, 0x2c);

		if (ptr < dmap->dmabuf_phys)
		  continue;

		ptr -= dmap->dmabuf_phys;

		if (ptr >= dmap->bytes_in_use)
		  continue;

		break;
	      }

	    ptr /= dmap->fragment_size;

	    if ((ptr < 0) || (ptr > dmap->nfrags))
	      ptr = 0;

	    n = 0;
	    while ((dmap_get_qtail (dmap) != ptr) && (n++ < dmap->nfrags))
	      oss_audio_inputintr (portc->audiodev, 0);
	  }
      }

  /* handle SPDIF output */
  if (status & 0x20)
    {
      portc = &devc->portc[2];
      if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)
	  && (portc->port_type == DF_SPDIF))
	{
	  dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
	  int ptr, n = 0;
	  int timeout = 1000;
	  serviced = 1;
	  while (timeout--)
	    {
	      ptr = READL (devc->osdev, 0x5c);
	      if (ptr < dmap->dmabuf_phys)
		continue;
	      ptr -= dmap->dmabuf_phys;
	      if (ptr >= dmap->bytes_in_use)
		continue;
	      break;
	    }
	  ptr /= dmap->fragment_size;

	  if ((ptr < 0) || (ptr > dmap->nfrags))
	    ptr = 0;

	  n = 0;
	  while ((dmap_get_qhead (dmap) != ptr) && (n++ < dmap->nfrags))
	    oss_audio_outputintr (portc->audiodev, 1);
	}
    }

  /* Acknowledge Interrupts */
  WRITEL (devc->osdev, status, 0x00);
  /* MUTEX_EXIT (devc->mutex, flags); */
  return serviced;
}

/*
 * Audio routines
 */

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

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

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

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

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

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

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

  return portc->channels;
}

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

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

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

  return portc->bits;
}

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

static void ATI_audio_trigger (int dev, int state);

static void
ATI_audio_reset (int dev)
{
  ATI_audio_trigger (dev, 0);
}

static void
ATI_audio_reset_input (int dev)
{
  ATI_portc *portc = audio_engines[dev]->portc;
  ATI_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
ATI_audio_reset_output (int dev)
{
  ATI_portc *portc = audio_engines[dev]->portc;
  ATI_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
ATI_audio_open (int dev, int mode, int openflags)
{
  ATI_portc *portc = audio_engines[dev]->portc;
  ATI_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;

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

  portc->open_mode = mode;
  portc->audio_enabled &= ~mode;
  devc->open_mode |= mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
ATI_audio_close (int dev, int mode)
{
  ATI_portc *portc = audio_engines[dev]->portc;
  ATI_devc *devc = audio_engines[dev]->devc;

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

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

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

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

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

static void
ATI_enable_dma (int dev, int direction, int enable)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;

  if (enable)
    {
      if (direction)
	{
	  /* flush fifo and enable analog or spdif DMA */
	  WRITEL (devc->osdev, READL (devc->osdev, 0x88) | 1, 0x88);

	  if (portc->port_type == DF_ANALOG)
	    WRITEL (devc->osp, READL (devc->osp, 0x08) | 1 << 9, 0x08);
	  else
	    WRITEL (devc->osp, READL (devc->osp, 0x08) | 1 << 10, 0x08);

	}
      else
	{
	  /* flush fifo */
	  WRITEL (devc->osdev, READL (devc->osdev, 0x88) | 2, 0x88);
	  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | 1 << 8, 0x08);
	}
    }
  else
    {
      if (direction)
	{
	  if (portc->port_type == DF_ANALOG)
	    WRITEL (devc->osp, READL (devc->osp, 0x08) & ~(1 << 9), 0x08);
	  else
	    WRITEL (devc->osp, READL (devc->osp, 0x08) & ~(1 << 10), 0x08);
	}
      else
	WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(1 << 8), 0x08);
    }
}

static void
ATI_audio_trigger (int dev, int state)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      ATI_enable_dma (dev, 1, 1);

	      /* set BDL & enable analog or SPDIF DMA */
	      if (portc->port_type == DF_ANALOG)
		{
		  WRITEL (devc->osp, devc->playBDL_phys | 1, 0x38);
		  WRITEL (devc->osp, READL (devc->osp, 0x08) | 0x04, 0x08);
		}
	      else		/* SPDIF */
		{
		  WRITEL (devc->osp, devc->spdifBDL_phys | 1, 0x50);
		  WRITEL (devc->osp, READL (devc->osp, 0x08) | 0x10, 0x08);
		}

	      /* set bus busy */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x04) | 1 << 14, 0x04);

	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;

	      ATI_enable_dma (dev, 1, 0);

	      /* Stop Anlog or SPDIF DMA */
	      if (portc->port_type == DF_ANALOG)
		WRITEL (devc->osp, READL (devc->osp, 0x08) & ~0x04, 0x08);
	      else
		WRITEL (devc->osp, READL (devc->osp, 0x08) & ~0x10, 0x08);

	      /* set bus clear */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x04) & ~(1 << 14),
		      0x04);

	    }
	}
    }

  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;
	      ATI_enable_dma (dev, 0, 1);
	      WRITEL (devc->osdev, devc->recBDL_phys | 1, 0x20);

	      /* enable audio intput and enable input DMA */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x08) | 0x02, 0x08);
	      /* set bus busy */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x04) | (1 << 14),
		      0x04);
	    }
	}
      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;
	      ATI_enable_dma (dev, 0, 0);
	      /* stop audio input */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~0x02, 0x08);
	      /* clear bus busy */
	      WRITEL (devc->osdev, READL (devc->osdev, 0x04) & ~(1 << 14),
		      0x04);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
ATI_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  int i, n;
  unsigned int data;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  /* set the bits */
  data = READL (devc->osdev, 0x08) & ~(1 << 21);
  if (portc->bits == 16)
    data |= (1 << 21);
  WRITEL (devc->osdev, data, 0x08);	/*set 8/16 bits */

  n = dmap->nfrags;
  if (n > BDL_SIZE)
    {
      cmn_err (CE_WARN, "Internal error - BDL too small\n");
      return -EIO;
    }

  for (i = 0; i < n; i++)
    {
      int next = i + 1;
      if (i == n - 1)
	next = 0;

      devc->recBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);
      devc->recBDL[i].status = 0;
      devc->recBDL[i].size = dmap->fragment_size >> 2;
      devc->recBDL[i].next = devc->recBDL_phys + next * sizeof (bdl_t);
    }

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

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

/*ARGSUSED*/
static int
ATI_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  int i, n;
  unsigned int data;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);


  if (portc->port_type == DF_SPDIF)
    {
      ac97_spdif_setup (devc->mixer_dev, portc->speed, portc->bits);

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

      n = dmap->nfrags;
      if (n > BDL_SIZE)
	n = BDL_SIZE;

      for (i = 0; i < n; i++)
	{
	  int next = i + 1;
	  if (i == n - 1)
	    next = 0;

	  devc->spdifBDL[i].addr =
	    dmap->dmabuf_phys + (i * dmap->fragment_size);
	  devc->spdifBDL[i].status = 0;
	  devc->spdifBDL[i].size = dmap->fragment_size >> 2;
	  devc->spdifBDL[i].next =
	    devc->spdifBDL_phys + next * sizeof (bdl_t);
	}

    }
  else
    {
      data = READL (devc->osdev, 0x34) & ~0x3ff;
      switch (portc->channels)
	{
	case 6:
	  data |= 0x30;		/* slots 7, 8 */
	  break;
	case 4:
	  data |= 0x48;		/* slts 6, 9 */
	  break;
	default:
	  data |= 0x03;		/* slots 3, 4 */
	  break;
	}
      data |= 4 << 11;
      WRITEL (devc->osdev, data, 0x34);

      /* set the bits */
      data = READL (devc->osdev, 0x08) & ~(1 << 22);
      if (portc->bits == 16)
	data |= (1 << 22);
      WRITEL (devc->osdev, data, 0x08);	/*set 8/16 bits */
#if 0
      /* set 6 channel reorder */
      if (portc->channels >= 6)
	WRITEL (devc->osdev, 0x1, 0x84);
      else
	WRITEL (devc->osdev, 0x00, 0x84);
#endif
      n = dmap->nfrags;
      if (n > BDL_SIZE)
	{
	  cmn_err (CE_WARN, "Internal error - BDL too small\n");
	  return -EIO;
	}

      for (i = 0; i < n; i++)
	{
	  int next = i + 1;
	  if (i == n - 1)
	    next = 0;

	  devc->playBDL[i].addr =
	    dmap->dmabuf_phys + (i * dmap->fragment_size);
	  devc->playBDL[i].status = 0;
	  devc->playBDL[i].size = dmap->fragment_size >> 2;
	  devc->playBDL[i].next = devc->playBDL_phys + next * sizeof (bdl_t);
	}
    }
  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static int
ATI_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  ATI_devc *devc = audio_engines[dev]->devc;
  ATI_portc *portc = audio_engines[dev]->portc;
  unsigned int f = 0;
  oss_native_word flags;
  int loop = 100;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  /* we need to read the regs a couple of times because of possible h/w bugs */
  while (loop--)
    {
      if (direction == PCM_ENABLE_OUTPUT)
	{
	  if (portc->port_type == DF_ANALOG)
	    f = READL (devc->osdev, 0x44);
	  else
	    f = READL (devc->osdev, 0x5c);
	}

      if (direction == PCM_ENABLE_INPUT)
	{
	  f = READL (devc->osdev, 0x2c);
	}

      if (f < dmap->dmabuf_phys)
	continue;

      f -= dmap->dmabuf_phys;

      if (f >= dmap->bytes_in_use)
	continue;

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

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

static const audiodrv_t ATI_audio_driver = {
  ATI_audio_open,
  ATI_audio_close,
  ATI_audio_output_block,
  ATI_audio_start_input,
  ATI_audio_ioctl,
  ATI_audio_prepare_for_input,
  ATI_audio_prepare_for_output,
  ATI_audio_reset,
  NULL,
  NULL,
  ATI_audio_reset_input,
  ATI_audio_reset_output,
  ATI_audio_trigger,
  ATI_audio_set_rate,
  ATI_audio_set_format,
  ATI_audio_set_channels,
  NULL,
  NULL,
  NULL,				/* ATI_check_input, */
  NULL,				/* ATI_check_output, */
  NULL,				/* ATI_alloc_buffer, */
  NULL,				/* ATI_free_buffer, */
  NULL,
  NULL,
  ATI_get_buffer_pointer
};

static int
init_ATI (ATI_devc * devc)
{
  int my_mixer, my_dev, opts;
  int i, timeout;
  oss_native_word phaddr;

  /* Power up */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~1, 0x08);
  oss_udelay (20);
  /* check if the ACLink is Active */
  timeout = 10;
  while (!(READL (devc->osdev, 0x08) & (1 << 28)))
    {
      WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 30), 0x08);	/* assert sync */
      oss_udelay (10);
      if (READL (devc->osdev, 0x08) & (1 << 28))
	break;

      /* set AC97 reset field to 0 */
      WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(0x80000000), 0x08);
      oss_udelay (10);

      /* set AC97 reset field to 1 */
      WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (0x80000000), 0x08);
      oss_udelay (10);

      if (READL (devc->osdev, 0x08) & (1 << 28))
	break;
      if (!--timeout)
	{
	  cmn_err (CE_WARN, "Timed out waiting for AC97 Link ready\n");
	  break;
	}
    }

  /* set ac97 reset field to 1: deassert */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (0x80000000), 0x08);

  /* do a soft reset */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 29), 0x08);
  oss_udelay (10);

  /* deassert softreset */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(1 << 29), 0x08);
  oss_udelay (10);

  /* do a sync */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 30), 0x08);
  oss_udelay (10);

  /* now continue with initializing the rest of the chip */
  WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 25), 0x08);	/*enable burst */

  WRITEL (devc->osdev, 0x22, 0x04);	/* enable audio+spdif interrupts */
  devc->bdlBuffer =
    CONTIG_MALLOC (devc->osdev, MAX_PORTC * BDL_SIZE * sizeof (bdl_t),
		   MEMLIMIT_32BITS, &phaddr);
  if (devc->bdlBuffer == NULL)
    {
      cmn_err (CE_WARN, "OSS Failed to allocate BDL\n");
      return 0;
    }

  devc->playBDL = (bdl_t *) devc->bdlBuffer;
  devc->playBDL_phys = phaddr;
  devc->recBDL =
    (bdl_t *) (devc->bdlBuffer + (1 * BDL_SIZE * sizeof (bdl_t)));
  devc->recBDL_phys = phaddr + (1 * BDL_SIZE * sizeof (bdl_t));
  devc->spdifBDL =
    (bdl_t *) (devc->bdlBuffer + (2 * BDL_SIZE * sizeof (bdl_t)));
  devc->spdifBDL_phys = phaddr + (2 * BDL_SIZE * sizeof (bdl_t));

/*
 * Init mixer
 */
  my_mixer =
    ac97_install (&devc->ac97devc, "AC97 Mixer", ac97_read, ac97_write, devc,
		  devc->osdev);

  if (my_mixer == -1)
    return 0;			/* No mixer */

  devc->mixer_dev = my_mixer;


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

  for (i = 0; i < MAX_PORTC; i++)
    {
      ATI_portc *portc = &devc->portc[i];
      char tmp_name[100];
      int formats = AFMT_S16_LE | AFMT_AC3;
      strcpy (tmp_name, devc->chip_name);
      opts = ADEV_AUTOMODE | ADEV_STEREOONLY | ADEV_FIXEDRATE;
      portc->port_type = DF_ANALOG;

      if (i == 0)
	{
	  opts |= ADEV_DUPLEX;
	  strcpy (tmp_name, devc->chip_name);
	}
      if (i == 1)
	{
	  strcpy (tmp_name, devc->chip_name);
	  opts |= ADEV_DUPLEX | ADEV_SHADOW;
	}
      if (i == 2)
	{
	  sprintf (tmp_name, "%s (SPDIF out)", devc->chip_name);
	  opts |= ADEV_NOINPUT | ADEV_SPECIAL;
	  portc->port_type = DF_SPDIF;
	  oss_audio_set_devname ("spdout");
	}

      if ((my_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					  devc->osdev,
					  devc->osdev,
					  tmp_name,
					  &ATI_audio_driver,
					  sizeof (audiodrv_t),
					  opts, formats, NULL, -1)) < 0)
	{
	  my_dev = -1;
	  return 0;
	}
      else
	{
	  audio_engines[my_dev]->devc = devc;
	  audio_engines[my_dev]->portc = portc;
	  audio_engines[my_dev]->mixer_dev = my_mixer;
	  audio_engines[my_dev]->min_rate =
	    (opts & ADEV_FIXEDRATE) ? 48000 : 5000;
	  audio_engines[my_dev]->max_rate = 48000;
	  audio_engines[my_dev]->caps |= PCM_CAP_FREERATE;
	  audio_engines[my_dev]->min_channels = 2;
	  audio_engines[my_dev]->max_channels = 6;

	  /*
	   * We can have at most BDL_SIZE fragments and they can
	   * be at most 128k each.
	   */
	  audio_engines[my_dev]->max_block = 128 * 1024;
	  audio_engines[my_dev]->max_fragments = BDL_SIZE;
	  portc->open_mode = 0;
	  portc->audio_enabled = 0;
	  portc->audiodev = my_dev;
	  if (audio_engines[my_dev]->flags & ADEV_FIXEDRATE)
	    audio_engines[my_dev]->fixed_rate = 48000;
	}
    }
  return 1;
}


int
atiaudio_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision /*, pci_latency */ ;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  ATI_devc *devc;

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

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

  if ((vendor != ATI_VENDOR_ID) || (device != ATI_DEVICE_IXP &&
				    device != ATI_DEVICE_IXP300
				    && device != ATI_DEVICE_IXP400))
    return 0;

  oss_pci_byteswap (osdev, 1);

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

  if (pci_ioaddr == 0)
    {
      cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n");
      return 0;
    }

  if (pci_irq_line == 0)
    {
      cmn_err (CE_WARN, "IRQ not assigned by BIOS (%d).\n", pci_irq_line);
      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;

  devc->membar_addr = pci_ioaddr;
  devc->membar_virt =
    (char *) MAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, 256);



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

#if 0
  latency = pci_read_config_dword (osdev, 0x50, &latency);
  pci_write_config_dword (osdev, 0x50, latency | 1);
#endif

  switch (device)
    {
    case ATI_DEVICE_IXP:
      devc->chip_name = "ATI IXP200";
      break;

    case ATI_DEVICE_IXP300:
      devc->chip_name = "ATI IXP300";
      break;

    case ATI_DEVICE_IXP400:
      devc->chip_name = "ATI IXP400";
      break;

    default:
      devc->chip_name = "ATI IXP";
      break;
    }

  devc->irq = pci_irq_line;
  devc->open_mode = 0;

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

  oss_register_device (osdev, devc->chip_name);

  if (oss_register_interrupts (devc->osdev, 0, ATIIXPintr, NULL) < 0)
    {
      cmn_err (CE_WARN, "Unable to register interrupts\n");
      return 0;
    }

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


int
atiaudio_detach (oss_device_t * osdev)
{
  ATI_devc *devc = (ATI_devc *) osdev->devc;

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

  WRITEL (devc->osdev, 0x3f, 0x00);	/* ack all interrupts */
  WRITEL (devc->osdev, 0, 0x04);	/* disable interrupts */

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

  oss_unregister_interrupts (devc->osdev);

  if (devc->bdlBuffer)
    CONTIG_FREE (devc->osdev, devc->bdlBuffer,
		 MAX_PORTC * BDL_SIZE * sizeof (bdl_t));

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

  if (devc->membar_addr != 0)
    {
      UNMAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, devc->membar_virt,
		     256);
      devc->membar_addr = 0;
    }

  oss_unregister_device (devc->osdev);
  return 1;

}
