/*
 * Purpose: Driver for Aureal Semiconductor Vortex PCI 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 "vortex.h"

#undef USE_SRC

#define AUREAL_VENDOR_ID	0x12eb
#define AUREAL_VORTEX		0x0001
#define AUREAL_VORTEX2		0x0002

#define global_base	(0x12800)
#	define			GIRQSTAT	global_base+0x0
#		define			MODIRQST	0x00004000
#		define			MIDIRQST	0x00002000
#		define			TIMIRQST	0x00001000
#		define			COIRQLST	0x00000800
#		define			COIRQPST	0x00000400
#		define			FRCIRQST	0x00000200
#		define			SBIRQST 	0x00000100
#		define			SBMIRQST	0x00000080
#		define			FMIRQST		0x00000040
#		define			DMAENDIRQST	0x00000020
#		define			DMABERRST	0x00000010
#		define			FIFOERRST	0x00000008
#		define			REGERRST	0x00000004
#		define			MPARERRST	0x00000002
#		define			MFATERRST	0x00000001
#	define			GIRQCTL		global_base+0x4
#	define			GSTAT		global_base+0x8
#	define			GCTL		global_base+0xc
#		define			SFTRST		0x00800000
#		define			ARBRST		0x00400000
#		define			EXTRST		0x00200000
#		define			RBTOEN		0x00100000
#		define			AINCEN		0x00080000
#		define			TTXFLUSH	0x00040000
#		define			TRVFLUSH	0x00020000
#		define			MFIFOFLUSH	0x00010000
#		define			FRCIRQ		0x00008000
#		define			GIRQEN		0x00004000

#define dma_base	(0x10000)

#define midi_base	(0x11000)
#	define			MIDIDAT		midi_base+0x0
#	define			MIDICMD		midi_base+0x4
#	define			MIDISTAT	MIDICMD
#		define			MIDIVAL		0x00000080
#		define			CMDOK		0x00000040
#		define			MPUMODE		0x00000001
#	define			GAMECTL		midi_base+0xc
#		define			JOYMODE		0x00000040
#		define			MIDITXFULL	0x00000008
#		define			MIDIRXINIT	0x00000004
#		define			MIDIRXOVFL	0x00000002
#		define			MIDIDATVAL	0x00000001

#define fifo_base	(0xe000)
#define adb_destinations		103
#define adbarb_block_base		(0x10800)
#define serial_block_base		(0x11800)
#	define			CODSMPLTMR	serial_block_base+0x19c
#define adbarb_wtdram_base_addr		(adbarb_block_base + 0x000)
#define adbarb_wtdram_srhdr_base_addr 	(adbarb_block_base + (adb_destinations * 4))
#define adbarb_sr_active_addr		(adbarb_block_base + 0x200)
#define adbarb_srb_last_addr		(adbarb_block_base + 0x204)
#define codec_control_reg_addr		(serial_block_base + 0x0184)
#define cmd_status_reg_addr		(serial_block_base + 0x0188)
#define channel_enable_reg_addr		(serial_block_base + 0x0190)
#define serial_ram_reg_addr		(serial_block_base + 0x00)

#define fifo_chan0_src_addr		0x00
#define fifo_chan1_src_addr		0x01
#define fifo_chan2_dst_addr		0x02
#define fifo_chan2a_dst_addr         0x022
#define fifo_chan3_dst_addr		0x03
#define fifo_chan3a_dst_addr         0x023
#define fifo_chan4_dst_addr		0x04
#define fifo_chan5_dst_addr		0x05
#define fifo_chan6_dst_addr		0x06
#define fifo_chan7_dst_addr		0x07
#define fifo_chan8_dst_addr		0x08
#define fifo_chan9_dst_addr		0x09
#define fifo_chana_dst_addr		0x0a
#define fifo_chanb_dst_addr		0x0b
#define src_chan0_src_addr		0x10
#define src_chan1_src_addr		0x11
#define src_chan0_dst_addr		0x10
#define src_chan1_dst_addr		0x11
#define codec_chan0_src_addr	0x48
#define codec_chan1_src_addr	0x49
#define codec_chan0_dst_addr	0x58
#define codec_chan1_dst_addr	0x59

#define src_base	(0xc000)
#define src_input_fifo				(src_base + 0x000)
#define src_output_double_buffer		(src_base + 0x800)
#define src_next_channel			(src_base + 0xc00)
#define src_sr_header				(src_base + 0xc40)
#define src_active_sample_rate			(src_base + 0xcc0)
#define src_throttle_source			(src_base + 0xcc4)
#define src_throttle_count_size			(src_base + 0xcc8)
#define src_channel_params			(src_base + 0xe00)

/* static int dst_routed[256] = { 0 }; */


static unsigned int
ReadReg (vortex_devc * devc, oss_native_word addr)
{
  return READL (addr);
}

static void
WriteReg (vortex_devc * devc, oss_native_word addr, unsigned int data)
{
  WRITEL (addr, data);
}

static void
ReadCodecRegister (vortex_devc * devc, int cIndex, int *pdwData)
{
  int dwCmdStRegAddr;
  int dwCmdStRegData;
  int nCnt = 0;

  dwCmdStRegAddr = cIndex << 16;
  WriteReg (devc, cmd_status_reg_addr, dwCmdStRegAddr);
  do
    {
      dwCmdStRegData = ReadReg (devc, cmd_status_reg_addr);
      if (nCnt++ > 10)
	oss_udelay (10);
    }
  while ((dwCmdStRegData & 0x00FF0000) != ((1 << 23) | (dwCmdStRegAddr))
	 && (nCnt < 100));
  if (nCnt >= 100)
    {
      *pdwData = dwCmdStRegData;
      cmn_err (CE_WARN,
	       "timeout waiting for Status Valid bit in the Command Status Reg, index %x\n",
	       cIndex);
    }
  else
    *pdwData = dwCmdStRegData & 0x0000ffff;
}

static void
WriteCodecCommand (vortex_devc * devc, int cIndex, int wData)
{
  int dwData;
  int nCnt = 0;

  do
    {
      dwData = ReadReg (devc, codec_control_reg_addr);
      if (nCnt++ > 10)
	oss_udelay (100);
    }
  while (!(dwData & 0x0100) && (nCnt < 100));
  if (nCnt >= 100)
    cmn_err (CE_WARN, "timeout waiting for Codec Command Write OK bit.\n");


  do
    {
      dwData = ReadReg (devc, codec_control_reg_addr);
      if (nCnt++ > 1)
	oss_udelay (100);
    }
  while (!(dwData & 0x0100) && (nCnt < 100));
  if (nCnt >= 100)
    cmn_err (CE_WARN, "timeout waiting for Codec Command Write OK bit.\n");

  dwData = (cIndex << 16) | (1 << 23) | wData;
  WriteReg (devc, cmd_status_reg_addr, dwData);
  oss_udelay (1000);

  /* Read it back to make sure it got there */
  do
    {
      dwData = ReadReg (devc, codec_control_reg_addr);
      if (nCnt++ > 1)
	oss_udelay (1000);
    }
  while (!(dwData & 0x0100) && (nCnt < 100));
  if (nCnt >= 100)
    cmn_err (CE_WARN, "timeout waiting for Codec Command Write OK bit.\n");
  ReadCodecRegister (devc, cIndex, &dwData);
  if (dwData != (int) wData)
    {
      do
	{
	  dwData = ReadReg (devc, codec_control_reg_addr);
	  if (nCnt++ > 1)
	    oss_udelay (1000);
	}
      while (!(dwData & 0x0100) && (nCnt < 100));
      if (nCnt >= 1000)
	cmn_err (CE_WARN,
		 "timeout waiting for Codec Command Write OK bit.\n");
      dwData = (cIndex << 16) | (1 << 23) | wData;
      WriteReg (devc, cmd_status_reg_addr, dwData);
      do
	{
	  dwData = ReadReg (devc, codec_control_reg_addr);
	  if (nCnt++ > 1)
	    oss_udelay (100);
	}
      while (!(dwData & 0x0100) && (nCnt < 50));
      if (nCnt >= 10)
	cmn_err (CE_WARN,
		 "timeout waiting for Codec Command Write OK bit.\n");
#if 0
      ReadCodecRegister (devc, cIndex, &dwData);
      if (dwData != (int) wData)
	{
	  cmn_err (CE_WARN,
		   "Vortex ERROR: Write to index %x failed (exp %04x, got %04x)\n",
		   cIndex, wData, dwData);
	}
#endif
    }
}

static int
ac97_read (void *devc_, int addr)
{
  vortex_devc *devc = devc_;
  int data;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  ReadCodecRegister (devc, addr, &data);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return data & 0xffff;
}

static int
ac97_write (void *devc_, int addr, int data)
{
  vortex_devc *devc = devc_;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  WriteCodecCommand (devc, addr, data);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}

static void
ClearDataFifo (vortex_devc * devc, int nChannel)
{
  int j;
  /* Clear out FIFO data */
  for (j = 0; j < 32; j++)
    WriteReg (devc, fifo_base + (0x80 * nChannel) + (0x4 * j), 0x0);
}

static int
vortexintr (oss_device_t * osdev)
{
  vortex_devc *devc = (vortex_devc *) osdev->devc;
  int status;
  int i;
  int serviced = 0;

  /*
   * TODO: Fix mutexes and move the inputintr/outputintr calls outside the
   * mutex block.
   */
  /* MUTEX_ENTER (devc->mutex, flags); */
  status = ReadReg (devc, GIRQSTAT);

#if 0
  if (status & MFATERRST)
    cmn_err (CE_WARN, "Master fatal error interrupt\n");
  if (status & MPARERRST)
    cmn_err (CE_WARN, "Master parity error interrupt\n");
#endif

  if (status & TIMIRQST)	/* Timer interrupt */
    {
      ReadReg (devc, CODSMPLTMR);	/* Clear the interrupt */
      WriteReg (devc, CODSMPLTMR, 0x00001000);
      serviced = 1;
    }

  if (status & DMAENDIRQST)	/* DMA end interrupt */
    {
      for (i = 0; i < MAX_PORTC; i++)
	{
	  vortex_portc *portc = &devc->portc[i];

	  if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    {
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;

	      int pos = 0, n;
	      unsigned int dmastat;

	      dmastat = ReadReg (devc, dma_base + 0x5c0);
	      pos = ((dmastat >> 12) & 0x03) * 4096 + (dmastat & 4095);

	      pos /= dmap->fragment_size - 1;
	      if (pos < 0 || pos >= dmap->nfrags)
		pos = 0;

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

	  if (portc->trigger_bits & PCM_ENABLE_INPUT)
	    {
	      dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in;

	      int pos = 0, n;
	      unsigned int dmastat;

	      dmastat = ReadReg (devc, dma_base + 0x5c8);

	      pos = ((dmastat >> 12) & 0x03) * 4096 + (dmastat & 4095);

	      pos /= dmap->fragment_size;
	      if (pos < 0 || pos >= dmap->nfrags)
		pos = 0;

	      n = 0;
	      while (dmap_get_qtail (dmap) != pos && n++ < dmap->nfrags)
		oss_audio_inputintr (portc->audiodev, 0);
	    }
	  ReadReg (devc, dma_base + 0x600);	/* Read Dma Status0 */
	  ReadReg (devc, dma_base + 0x604);	/* Read Dma Status1 */
	  ReadReg (devc, dma_base + 0x608);	/* Read Dma Status2 */
	  serviced = 1;
	}
    }

  if (status & MIDIRQST)	/* MIDI interrupt */
    {
      int uart_stat = ReadReg (devc, MIDISTAT);
      int n = 10;

      while (!(uart_stat & MIDIVAL) && n--)
	{
	  int d;
	  d = ReadReg (devc, MIDIDAT) & 0xff;

	  if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
	    devc->midi_input_intr (devc->midi_dev, d);
	  uart_stat = ReadReg (devc, MIDISTAT);
	}
      serviced = 1;
    }
  if (status != 0)
    {
      WriteReg (devc, GIRQSTAT, status & 0x7ff);	/* Ack pulse interrupts */
      serviced = 1;
    }

  /* MUTEX_EXIT (devc->mutex, flags); */
  return serviced;
}

static void
cold_reset (vortex_devc * devc)
{
  int i;
  int bSigmatelCodec = 0;

  for (i = 0; i < 32; i = i + 1)
    {
      WriteReg (devc, serial_ram_reg_addr + 0x80 + (i * 4), 0);
      oss_udelay (100);
    }
  if (bSigmatelCodec)
    {
      /* Disable clock */
      WriteReg (devc, codec_control_reg_addr, 0x00a8);
      oss_udelay (100);
      /* Set Sync High     */
      /*    WriteReg(devc, codec_control_reg_addr, 0x40a8); */
      /*    delay(100);    */
      /* Place CODEC into reset */
      WriteReg (devc, codec_control_reg_addr, 0x80a8);
      oss_udelay (100);
      /* Give CODEC some Clocks with reset asserted */
      WriteReg (devc, codec_control_reg_addr, 0x80e8);
      oss_udelay (100);
      /* Turn off clocks */
      WriteReg (devc, codec_control_reg_addr, 0x80a8);
      oss_udelay (100);
      /* Take out of reset */
      /*  WriteReg(devc, codec_control_reg_addr, 0x40a8); */
      /*  oss_udelay(100);    */
      /* Release reset     */
      WriteReg (devc, codec_control_reg_addr, 0x00a8);
      oss_udelay (100);
      /* Turn on clocks        */
      WriteReg (devc, codec_control_reg_addr, 0x00e8);
      oss_udelay (100);
    }
  else
    {
      WriteReg (devc, codec_control_reg_addr, 0x8068);
      oss_udelay (10);
      WriteReg (devc, codec_control_reg_addr, 0x00e8);
      oss_udelay (10);
    }
}

static void
InitCodec (vortex_devc * devc)
{
  int i;
  cold_reset (devc);
  for (i = 0; i < 32; i = i + 1)
    {
      WriteReg (devc, serial_ram_reg_addr + 0x80 + (i * 4), 0);
      oss_udelay (10);
    }
  /* Set up the codec in AC97 mode */
  WriteReg (devc, codec_control_reg_addr, 0x00e8);
  oss_udelay (10);
  /* Clear the channel enable register */
  WriteReg (devc, channel_enable_reg_addr, 0);
}

static void
SetupCodec (vortex_devc * devc)
{
  int dwData;
  int count = 0;
  int dwBit26 = 1 << 26;

  /* do the following only for ac97 codecs */
  /* Wait for Codec Ready (bit 26) */
  do
    {
      dwData = ReadReg (devc, codec_control_reg_addr);
      if (count++ > 1)
	oss_udelay (10);
    }
  while ((count < 100) && !(dwData & dwBit26));
  if (count >= 100)
    {
      cmn_err (CE_WARN, "Error: timeout waiting for Codec Ready bit.\n");
      cmn_err (CE_WARN, "Codec Interface Control Register is %08x\n", dwData);
    }
  /* Write interesting data to the Codec 97 Mixer registers */
  /* Master Volume 0dB Attunuation, Not muted. */
  WriteCodecCommand (devc, 0x02, 0x0f0f);
  oss_udelay (10);
  /* Master Volume mono muted. */
  WriteCodecCommand (devc, 0x06, 0x8000);
  oss_udelay (10);
  /* Mic Volume muted. */
  WriteCodecCommand (devc, 0x0e, 0x8000);
  oss_udelay (10);
  /* Line In Volume muted. */
  WriteCodecCommand (devc, 0x10, 0x8000);
  oss_udelay (10);
  /* CD Volume muted. */
  WriteCodecCommand (devc, 0x12, 0x8000);
  oss_udelay (10);
  /* Aux Volume muted. */
  WriteCodecCommand (devc, 0x16, 0x8000);
  oss_udelay (10);
  /* PCM out Volume 0 dB Gain, Not muted. */
  WriteCodecCommand (devc, 0x18, 0x0f0f);
  oss_udelay (10);
  /* Record select, select Mic for recording */
  WriteCodecCommand (devc, 0x1a, 0x0404);
  oss_udelay (10);
  /* Record Gain, 0dB */
  WriteCodecCommand (devc, 0x1c, 0x8000);
  oss_udelay (10);

  /* Poll the Section Ready bits in the Status Register (index 0x26) */
  count = 0;
  do
    {
      ReadCodecRegister (devc, 0x26, &dwData);
      if (count++ > 1)
	oss_udelay (10);
    }
  while (!(dwData & 0x02) && (count < 10));
  if (!(dwData & 0x02))
    cmn_err (CE_WARN, "DAC section ready bit is not set.\n");

  /* Read and confirm the data in the Codec 97 Mixer registers. */
  /* just the PCM reg, as a sanity check */
  ReadCodecRegister (devc, 0x18, &dwData);
  if ((dwData & 0x0000ffff) != 0xf0f)
    {
      cmn_err (CE_WARN, "PCM volume reg is %x, sb 0xf0f.\n", dwData);
    }
}

static void
InitAdb (vortex_devc * devc)
{
/*  parameter  values for write_op  */
#define op_none	  0		/* dst_op = x */
#define op_tail 	  1	/* dst_op = x */
#define op_add 	  2		/* dst_op = dst_addr being added */
#define op_adds 	  3	/* dst_op = dst_addr being added */
#define op_del		  4	/* dst_op = dst_addr being deleted */
#define op_dels	  5		/* dst_op = dst_addr being deleted */
#define op_inval	  6	/* dst_op = x */

  int i;
#if 0
  unsigned char /*reg   [3:0] */ write_op;
  unsigned char /*reg   [6:0] */ dst_op;
  unsigned char /*reg   [6:0] */ src_op;
  unsigned char /*reg   [SS:0] */ sr_op;
#endif
  /* misc */

  devc->sr_active = 0;
  /* the initial tail for each list is the header location */
  for (i = 0; i <= 31; i = i + 1)
    devc->tail_index[i] = adb_destinations + i;
  for (i = 0; i <= 127; i = i + 1)
    devc->dst_index[i] = 0x7f;	/*~('b0); */
  for (i = 0; i <= 127; i = i + 1)
    devc->sr_list[i] = 0x1f;	/*~('b0); */
#if 0
  write_op = op_none;
  dst_op = 0x7f;		/* ~('b0); */
  src_op = 0x7f;		/* ~('b0); */
  sr_op = 0x1f;			/* ~('b0); */
#endif
  /* Disable any active sample rate */
  WriteReg (devc, 0x10800 + 0x200, 0);
  /* Null out all the linked lists */
  for (i = 0; i < 0x1f0; i = i + 4)
    {
      WriteReg (devc, 0x10800 + i, 0xffffffff);
    }
}

static void
SetBit (unsigned int owData[], int nBit, unsigned char cVal)
{
  if (cVal)
    owData[nBit / 32] |= (1 << (nBit % 32));
  else
    owData[nBit / 32] &= ~(1 << (nBit % 32));
}

/*ARGSUSED*/
static int
add_route (vortex_devc * devc, unsigned int sr,
	   unsigned int src_addr, unsigned int dst_addr, int verify)
{

  unsigned int ram_hdr_addr;
  unsigned int ram_dst_addr;
  unsigned int ram_tail_addr;
  unsigned int sr_ram_index;

  sr_ram_index = adb_destinations + sr;
  ram_hdr_addr = adbarb_wtdram_srhdr_base_addr + (sr * 4);	/* VHRDxx[sr] */
  ram_dst_addr = adbarb_wtdram_base_addr + (dst_addr * 4);	/* VDSTxx[dst_addr] */
  ram_tail_addr = adbarb_wtdram_base_addr + (devc->tail_index[sr] * 4);

  /* since always add to end of list, ram[dst_addr] will be the new tail. */
  /* and, since we could be adding to an active list, the tail needs */
  /* to be NULLed before the new link is inserted  */
  /* (since we need to check the current tail next, devc->tail_index is  */
  /* updated a bit later below.) */
  WriteReg (devc, ram_dst_addr, 0xffffffff);

  /* check if this sr has a list started yet */
  if (devc->tail_index[sr] == (adb_destinations + sr))
    {
      /* current tail for this sample rate indicates that list is empty, */
      /* thus this route will be head of list */
      WriteReg (devc, ram_hdr_addr, ((src_addr << 7) | dst_addr));
      devc->dst_index[dst_addr] = sr_ram_index;
    }
  else
    {
      /* add to end of list */
      WriteReg (devc, ram_tail_addr, ((src_addr << 7) | dst_addr));
      devc->dst_index[dst_addr] = devc->tail_index[sr];
    }

  /* keep track of the new tail */
  devc->tail_index[sr] = dst_addr;

  /* keep track of which sample rate list this dst_addr now belongs to */
  devc->sr_list[dst_addr] = sr;

  /* mark dst_addr as routed */
  /* dst_routed[dst_addr] = 1; */
  SetBit ((unsigned int *) &devc->dst_routed, dst_addr, 1);

  return 0;
}

/*ARGSUSED*/
static void
del_route (vortex_devc * devc, unsigned char dst_addr, int verify)
{

  unsigned int data, ram_dst_addr, ram_rtd_addr;
  unsigned char sr;

  ram_dst_addr = adbarb_wtdram_base_addr + (dst_addr * 4);
  ram_rtd_addr = adbarb_wtdram_base_addr + (devc->dst_index[dst_addr] * 4);

  /* get the sr list that this dst_addr belongs to */
  sr = devc->sr_list[dst_addr];

  /* mark dst as no longer routed  */
  SetBit ((unsigned int *) &devc->dst_routed, dst_addr, 0);

  /* remove the dst from the sr_list then check to see if this was the */
  /* last route in the list.  if so, disable wtd for this sample rate */
  devc->sr_list[dst_addr] = 0x1f;	/*~('b0); */

  /* find out what's linked to us so we can reroute it.  if we are the  */
  /* tail, this will be NULL and will get relinked as such */
  data = ReadReg (devc, ram_dst_addr);

  /* now update the list by writing what was linked to us to where we  */
  /* were once at in the list  */
  WriteReg (devc, ram_rtd_addr, data);

  /* update the devc->dst_index for this reroute */
  /* devc->dst_index[data[6:0]] = devc->dst_index[dst_addr]; */
  devc->dst_index[data & 0x7f] = devc->dst_index[dst_addr];

  /* Invalidate the now deleted route.  Data for dst_addr = 7e; */
  /* NOTE: the adbarb_monitor needs this write to track properly! */
  WriteReg (devc, ram_dst_addr, 0xfffffffe);

  /* if we are removing the tail, reset the tail pointer */
  if (devc->tail_index[sr] == dst_addr)
    {
      devc->tail_index[sr] = devc->dst_index[dst_addr];
    }

  /* clean up all data elements used to track this dst_addr */
  /* XXX check field size below */
  devc->dst_index[dst_addr] = 0x7f;	/* ~('b0); */
}

static void
SetupRoutes (vortex_devc * devc)
{
  /* First add the record routes */
  add_route (devc, 17, codec_chan0_src_addr, fifo_chan2_dst_addr, 0);
  add_route (devc, 17, codec_chan1_src_addr, fifo_chan3_dst_addr, 0);
  add_route (devc, 17, codec_chan0_src_addr, fifo_chan2a_dst_addr, 0);
  add_route (devc, 17, codec_chan1_src_addr, fifo_chan3a_dst_addr, 0);

  /* Now add the playback routes */
  add_route (devc, 17, fifo_chan0_src_addr, codec_chan0_dst_addr, 0);
  add_route (devc, 17, fifo_chan0_src_addr, codec_chan1_dst_addr, 0);
}

static void
EnableCodecChannel (vortex_devc * devc, unsigned char channel)
{
  unsigned int data;

  data = ReadReg (devc, channel_enable_reg_addr);
  data = data | (1 << (8 + channel));	/*(1'b1 << (8+channel)); */
  WriteReg (devc, channel_enable_reg_addr, data);
}

static void
DisableCodecChannel (vortex_devc * devc, unsigned char channel)
{
  unsigned int data;

  data = ReadReg (devc, channel_enable_reg_addr);
  data = data & ~(1 << (8 + channel));	/*~(1'b1 << (8+channel)); */
  WriteReg (devc, channel_enable_reg_addr, data);
}

static void
EnableAdbWtd (vortex_devc * devc, int sr)
{
  unsigned int dwData;

  dwData = ReadReg (devc, adbarb_sr_active_addr);
  dwData |= (1 << sr);
  WriteReg (devc, adbarb_sr_active_addr, dwData);
  devc->sr_active |= (1 << sr);
}

static void
DisableAdbWtd (vortex_devc * devc, int sr)
{
  unsigned int dwData;

  dwData = ReadReg (devc, adbarb_sr_active_addr);
  dwData &= ~(1 << sr);
  WriteReg (devc, adbarb_sr_active_addr, dwData);
  devc->sr_active &= ~(1 << sr);
}

/*ARGSUSED*/
static int
vortex_midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
		  oss_midi_inputbuf_t inputbuf,
		  oss_midi_outputintr_t outputintr)
{
  vortex_devc *devc = (vortex_devc *) midi_devs[dev]->devc;

  if (devc->midi_opened)
    {
      return -EBUSY;
    }

  devc->midi_input_intr = inputbyte;
  devc->midi_opened = mode;

  if (mode & OPEN_READ)
    {
      int tmp = ReadReg (devc, GIRQCTL);
      WriteReg (devc, GIRQCTL, tmp | MIDIRQST);	/* Enable MIDI interrupts */
      tmp = ReadReg (devc, GIRQCTL);
    }

  WriteReg (devc, MIDICMD, 0x000000ff);	/* Reset MIDI */
  WriteReg (devc, MIDICMD, 0x0000003f);	/* Enter UART mode */
  if ((ReadReg (devc, MIDIDAT) & 0xff) != 0xfe)
    cmn_err (CE_WARN, "Vortex warning! MIDI init not acknowledged\n");
  return 0;
}

/*ARGSUSED*/
static void
vortex_midi_close (int dev, int mode)
{
  vortex_devc *devc = (vortex_devc *) midi_devs[dev]->devc;

  int tmp = ReadReg (devc, GIRQCTL);
  WriteReg (devc, GIRQCTL, tmp & ~MIDIRQST);	/* Disable MIDI interrupts */
  WriteReg (devc, MIDICMD, 0x000000ff);	/* Reset MIDI */

  devc->midi_opened = 0;
}

static int
vortex_midi_out (int dev, unsigned char midi_byte)
{
  int n = 10;
  vortex_devc *devc = (vortex_devc *) midi_devs[dev]->devc;

  while ((ReadReg (devc, MIDISTAT) & CMDOK) && n--);
  if (ReadReg (devc, MIDISTAT) & CMDOK)
    return 0;
  WriteReg (devc, MIDIDAT, midi_byte);
  return 1;
}

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

static midi_driver_t vortex_midi_driver = {
  vortex_midi_open,
  vortex_midi_close,
  vortex_midi_ioctl,
  vortex_midi_out
};

static void
vortex_midi_init (vortex_devc * devc)
{
  /* Derive the MIDI baud rate from 49.152 MHz clock */
  WriteReg (devc, GAMECTL, 0x00006100);
  WriteReg (devc, MIDICMD, 0x000000ff);	/* Reset MIDI */
  WriteReg (devc, MIDICMD, 0x0000003f);	/* Enter UART mode */

  /* All commands should return 0xfe as an acknowledgement */
  if ((ReadReg (devc, MIDIDAT) & 0xff) != 0xfe)
    cmn_err (CE_WARN, "Vortex warning! MIDI init not acknowledged\n");
  WriteReg (devc, MIDICMD, 0x000000ff);	/* Reset MIDI */
}

/***********************************
 * Audio routines 
 ***********************************/

static int
vortex_set_rate (int dev, int arg)
{
  vortex_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
vortex_set_channels (int dev, short arg)
{
  vortex_portc *portc = audio_engines[dev]->portc;

  if (audio_engines[dev]->flags & ADEV_STEREOONLY)
    arg = 2;

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

  return portc->channels;
}

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

  if (audio_engines[dev]->flags & ADEV_16BITONLY)
    arg = 16;

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

  return portc->bits;
}

/*ARGSUSED*/
static int
vortex_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
#if 0
  vortex_portc *portc = audio_engines[dev]->portc;
  int val;

  switch (cmd)
    {
    case SNDCTL_DSP_SETOUTVOL:
      IOCTL_GET (arg, val);
      return IOCTL_OUT (arg, softoss_avol (audio_engines[dev]->mixer_dev,
					   portc->voice_chn,
					   SNDCTL_MIX_WRITE, val));
      break;

    case SNDCTL_DSP_GETOUTVOL:
      return IOCTL_OUT (arg, softoss_avol (audio_engines[dev]->mixer_dev,
					   portc->voice_chn,
					   SNDCTL_MIX_READ, 0));
      break;

    }

#endif
  return -EINVAL;
}

#ifdef USE_SRC
static void cleanup_src (int dev);
#endif

static void vortex_trigger (int dev, int state);

static void
vortex_reset (int dev)
{
  vortex_trigger (dev, 0);

#ifdef USE_SRC
  cleanup_src (dev);
  del_route (devc, src_chan0_dst_addr, 0);
  del_route (devc, src_chan1_dst_addr, 0);
#endif
}

static void
vortex_reset_input (int dev)
{
  vortex_portc *portc = audio_engines[dev]->portc;
  vortex_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
vortex_reset_output (int dev)
{
  vortex_portc *portc = audio_engines[dev]->portc;
  vortex_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
vortex_open (int dev, int mode, int open_flags)
{
  oss_native_word flags;
  vortex_portc *portc = audio_engines[dev]->portc;
  vortex_devc *devc = audio_engines[dev]->devc;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->open_mode != 0)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return -EBUSY;
    }

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

  devc->open_mode |= mode;

  portc->open_mode = mode;
  portc->audio_enabled &= ~mode;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
vortex_close (int dev, int mode)
{
  vortex_portc *portc = audio_engines[dev]->portc;
  vortex_devc *devc = audio_engines[dev]->devc;

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

/*
 * InitFifos
 */
static void
init_fifos (vortex_devc * devc)
{
  int i;

  /* Zero Out the FIFO Pointers */
  for (i = 0; i < 48; i++)
    {
      WriteReg (devc, fifo_base + 0x1800 + (4 * i), 0x00000020);
    }
  for (i = 2; i < 6; i++)
    {
      WriteReg (devc, fifo_base + 0x1800 + (4 * i), 0x00000000);
    }
  WriteReg (devc, fifo_base + 0x18c0, 0x00000843);
  /* Clear out FIFO data */
  for (i = 0; i < 4; i++)
    ClearDataFifo (devc, i);

  /* Set up the DMA engine to grab DMA memory */
  WriteReg (devc, dma_base + 0x61c, 0);	/* Clear Dma Status0 */
  WriteReg (devc, dma_base + 0x620, 0);	/* Clear Dma Status1 */
  WriteReg (devc, dma_base + 0x624, 0);	/* Clear Dma Status2 */
}

#ifdef USE_SRC
static void
setup_src (int dev)
{
  vortex_portc *portc = audio_engines[dev]->portc;
  vortex_devc *devc = audio_engines[dev]->devc;

  int i, j, chn;

  for (j = 0; j < 2; j++)
    {
      unsigned int tmp, ratio, link;
      chn = portc->voice_chn + j;

      for (i = 0; i < 128; i += 4)
	WriteReg (devc, src_input_fifo + (128 * chn) + i, 0);
      for (i = 0; i < 8; i += 4)
	WriteReg (devc, src_output_double_buffer + (chn * 8) + i, 0);

      ratio = 48000 / portc->speed;
      tmp = 0;
      tmp |= chn & 0xf;		/* Correlated channel */
      if (ratio > 4)
	tmp |= ((17 - ratio - 1) << 4);
      else
	tmp |= (12 << 4);	/* Zero crossing */
      WriteReg (devc, src_channel_params + 0xe00 + 4 * chn, tmp);	/* [0] */

      ratio = (48000 << 14) / portc->speed;
      WriteReg (devc, src_channel_params + 0xe40 * 4 * chn, ratio);	/* [1] */

      WriteReg (devc, src_channel_params + 0xe80 + 4 * chn, 0);	/* [2] */
      WriteReg (devc, src_channel_params + 0xec0 + 4 * chn, 0);	/* [3] */
      WriteReg (devc, src_channel_params + 0xf00 + 4 * chn, 0);	/* [4] */
      WriteReg (devc, src_channel_params + 0xf40 + 4 * chn, 1);	/* [5] */

      ratio = 48000 / portc->speed;
      tmp = 0x3000f;		/* Throttle in, FIFO depth=15 */
      WriteReg (devc, src_channel_params + 0xf80 + 4 * chn, tmp);	/* [6] */

      link = ReadReg (devc, src_sr_header + 0);
      WriteReg (devc, src_next_channel + chn * 4, link);
      WriteReg (devc, src_sr_header + 0, 0x10 | chn);

      link = ReadReg (devc, src_throttle_source);
      link |= (1 << chn);
      WriteReg (devc, src_throttle_source, link);

      link = ReadReg (devc, src_active_sample_rate);
      link |= (1 << 0);
      WriteReg (devc, src_active_sample_rate, link);

    }
}

static void
cleanup_src (int dev)
{
  vortex_portc *portc = audio_engines[dev]->portc;
  vortex_devc *devc = audio_engines[dev]->devc;

  int i, j, chn;

  for (j = 0; j < 2; j++)
    {
      chn = portc->voice_chn + j;

      for (i = 0; i < 128; i += 4)
	WriteReg (devc, src_input_fifo + (128 * chn) + i, 0);
      for (i = 0; i < 8; i += 4)
	WriteReg (devc, src_output_double_buffer + (chn * 8) + i, 0);
      WriteReg (devc, src_next_channel + chn * 4, 0);
      WriteReg (devc, src_sr_header + 0, 0);

      WriteReg (devc, src_active_sample_rate, 0);

    }
}
#endif

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

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

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

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

static void
vortex_trigger (int dev, int state)
{
  vortex_devc *devc = audio_engines[dev]->devc;
  vortex_portc *portc = audio_engines[dev]->portc;
  unsigned int fifo_mode;
  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))
	    {
	      /* Start the FIFOs */
	      fifo_mode = 0x30030;
	      if (portc->channels == 2)
		{
		  fifo_mode |= 0x00000002;
		  WriteReg (devc, fifo_base + 0x1800, fifo_mode);	/* Left Pb */
		}
	      else
		{
		  WriteReg (devc, fifo_base + 0x1800, fifo_mode);	/* Left Pb */
		  WriteReg (devc, fifo_base + 0x1804, fifo_mode);	/* Right Pb */
		}

	      WriteReg (devc, CODSMPLTMR, 0x1000);	/* start timer */
	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
	      portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;

	      WriteReg (devc, CODSMPLTMR, 0x0);	/* stop timer */
	      WriteReg (devc, fifo_base + 0x1800, 0);	/* Left Playback */
	      WriteReg (devc, fifo_base + 0x1804, 0);	/* Right Playback */

	      ClearDataFifo (devc, 0);
	      ClearDataFifo (devc, 1);
	    }
	}
    }
  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      /* Start the FIFOs */
	      fifo_mode = 0x30010;
	      if (portc->channels == 2)
		fifo_mode |= 0x00000002;
	      WriteReg (devc, fifo_base + 0x1808, fifo_mode);	/* Left Rec */
	      WriteReg (devc, fifo_base + 0x180c, fifo_mode);	/* Right Rec */
	      WriteReg (devc, CODSMPLTMR, 0x1000);	/* start timer */
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->trigger_bits &= ~PCM_ENABLE_INPUT;
	      portc->audio_enabled &= ~PCM_ENABLE_INPUT;

	      WriteReg (devc, CODSMPLTMR, 0x0);	/* stop timer */

	      WriteReg (devc, fifo_base + 0x1808, 0);	/* Left Record */
	      WriteReg (devc, fifo_base + 0x180c, 0);	/* Right Record */

	      ClearDataFifo (devc, 2);
	      ClearDataFifo (devc, 3);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

/*ARGSUSED*/
static int
vortex_prepare_for_input (int dev, int bsize, int bcount)
{
  unsigned int nBufSize, ChSizeGotoReg0, ChSizeGotoReg1, ch_mode;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  vortex_devc *devc = audio_engines[dev]->devc;
  vortex_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;
  int SAMPLES = 1024;


  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

#ifdef USE_SRC
  setup_src (dev);
  add_route (devc, 17, src_chan0_src_addr, codec_chan0_dst_addr, 0);
  add_route (devc, 17, src_chan1_src_addr, codec_chan1_dst_addr, 0);

  add_route (devc, 0, fifo_chan0_src_addr, src_chan0_dst_addr, 0);
  if (portc->channels == 2)
    add_route (devc, 0, fifo_chan0_src_addr, src_chan1_dst_addr, 0);
  else
    add_route (devc, 0, fifo_chan1_src_addr, src_chan1_dst_addr, 0);
#endif

  nBufSize = (SAMPLES * 2 * 2) - 1;

  ch_mode = 0x00001000;

  switch (portc->bits)
    {
    case AFMT_U8:
      ch_mode |= 0x00004000;
      break;

    case AFMT_S16_LE:
      ch_mode |= 0x00020000;
      break;

    case AFMT_MU_LAW:
      ch_mode |= 0x00008000;
      break;

    case AFMT_A_LAW:
      ch_mode |= 0x0000c000;
      break;

    }

  /* Left Record Channel VDB Chan 2  */
  WriteReg (devc, dma_base + 0x220, dmap->dmabuf_phys);	/* Set Chan0 Address */
  WriteReg (devc, dma_base + 0x224, dmap->dmabuf_phys + 4096);	/* Set Chan0 Address */
  WriteReg (devc, dma_base + 0x228, dmap->dmabuf_phys + 2 * 4096);	/* Set Chan0 Address */
  WriteReg (devc, dma_base + 0x22c, dmap->dmabuf_phys + 3 * 4096);	/* Set Chan0 Address */

  ChSizeGotoReg0 = (0xde000000) | (nBufSize << 12) | (nBufSize);
  ChSizeGotoReg1 = (0xfc000000) | (nBufSize << 12) | (nBufSize);
  WriteReg (devc, dma_base + 0x410, ChSizeGotoReg0);	/* Set Chan0 Size to SAMPLES*2*2, Loop over 0 */
  WriteReg (devc, dma_base + 0x414, ChSizeGotoReg1);	/* Set Chan0 Size to SAMPLES*2*2, Loop over 0 */
  WriteReg (devc, dma_base + 0x588, ch_mode);	/* Set Chan0 Mode */

  /* Right Record Channel VDB Chan 3 */
  WriteReg (devc, dma_base + 0x230, dmap->dmabuf_phys);	/* Set Chan1 Address */
  WriteReg (devc, dma_base + 0x234, dmap->dmabuf_phys + 4096);	/* Set Chan0 Address */
  WriteReg (devc, dma_base + 0x238, dmap->dmabuf_phys + 2 * 4096);	/* Set Chan0 Address */
  WriteReg (devc, dma_base + 0x23c, dmap->dmabuf_phys + 3 * 4096);	/* Set Chan0 Address */

  ChSizeGotoReg0 = (0x56000000) | (nBufSize << 12) | (nBufSize);
  ChSizeGotoReg1 = (0x74000000) | (nBufSize << 12) | (nBufSize);
  WriteReg (devc, dma_base + 0x418, ChSizeGotoReg0);
  WriteReg (devc, dma_base + 0x41c, ChSizeGotoReg1);
  WriteReg (devc, dma_base + 0x58c, ch_mode);	/* Set Chan1 Mode */

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

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

/*ARGSUSED*/
static int
vortex_prepare_for_output (int dev, int bsize, int bcount)
{
  unsigned int nBufSize, ChSizeGotoReg0, ChSizeGotoReg1, ch_mode;
  int SAMPLES = 1024;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  vortex_devc *devc = audio_engines[dev]->devc;
  vortex_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

#ifdef USE_SRC
  setup_src (dev);
  add_route (devc, 17, src_chan0_src_addr, codec_chan0_dst_addr, 0);
  add_route (devc, 17, src_chan1_src_addr, codec_chan1_dst_addr, 0);

  add_route (devc, 0, fifo_chan0_src_addr, src_chan0_dst_addr, 0);
  if (portc->channels == 2)
    add_route (devc, 0, fifo_chan0_src_addr, src_chan1_dst_addr, 0);
  else
    add_route (devc, 0, fifo_chan1_src_addr, src_chan1_dst_addr, 0);
#endif

  nBufSize = (SAMPLES * 2 * 2) - 1;

  ch_mode = 0x00003000;

  switch (portc->bits)
    {
    case AFMT_U8:
      ch_mode |= 0x00004000;
      break;

    case AFMT_S16_LE:
      ch_mode |= 0x00020000;
      break;

    case AFMT_MU_LAW:
      ch_mode |= 0x00008000;
      break;

    case AFMT_A_LAW:
      ch_mode |= 0x0000c000;
      break;

    }

  /* Left Playback Channel VDB Chan 0 */
  WriteReg (devc, dma_base + 0x200, dmap->dmabuf_phys);	/* Set Chan0 Address */
  WriteReg (devc, dma_base + 0x204, dmap->dmabuf_phys + 4096);	/* Set Chan0 Address */
  WriteReg (devc, dma_base + 0x208, dmap->dmabuf_phys + 2 * 4096);	/* Set Chan0 Address */
  WriteReg (devc, dma_base + 0x20c, dmap->dmabuf_phys + 3 * 4096);	/* Set Chan0 Address */

  ChSizeGotoReg0 = (0xde000000) | (nBufSize << 12) | (nBufSize);
  ChSizeGotoReg1 = (0xfc000000) | (nBufSize << 12) | (nBufSize);
  WriteReg (devc, dma_base + 0x400, ChSizeGotoReg0);	/* Set Chan0 Size to SAMPLES*2*2, Loop over 0 */
  WriteReg (devc, dma_base + 0x404, ChSizeGotoReg1);	/* Set Chan0 Size to SAMPLES*2*2, Loop over 0 */
  WriteReg (devc, dma_base + 0x580, ch_mode);	/* Set Chan0 Mode */

  /* Right Playback Channel VDB Chan 1 */
  WriteReg (devc, dma_base + 0x210, dmap->dmabuf_phys);	/* Set Chan1 Address */
  WriteReg (devc, dma_base + 0x214, dmap->dmabuf_phys + 4096);	/* Set Chan1 Address */
  WriteReg (devc, dma_base + 0x218, dmap->dmabuf_phys + 2 * 4096);	/* Set Chan1 Address */
  WriteReg (devc, dma_base + 0x21c, dmap->dmabuf_phys + 3 * 4096);	/* Set Chan1 Address */

  ChSizeGotoReg0 = (0x56000000) | (nBufSize << 12) | (nBufSize);
  ChSizeGotoReg1 = (0x74000000) | (nBufSize << 12) | (nBufSize);
  WriteReg (devc, dma_base + 0x408, ChSizeGotoReg0);
  WriteReg (devc, dma_base + 0x40c, ChSizeGotoReg1);

  WriteReg (devc, dma_base + 0x584, ch_mode);	/* Set Chan1 Mode */

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

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

/*ARGSUSED*/
static int
vortex_free_buffer (int dev, dmap_t * dmap, int direction)
{
  vortex_devc *devc = audio_engines[dev]->devc;

  if (dmap->dmabuf == NULL)
    return 0;
#if 1
  CONTIG_FREE (devc->osdev, dmap->dmabuf, dmap->buffsize);
#ifdef linux
  oss_unreserve_pages ((oss_native_word) dmap->dmabuf,
		       (oss_native_word) dmap->dmabuf + 4 * 4096 - 1);
#endif
#else
  dmap->buffsize += 4096;	/* Return the stolen page back */
  oss_free_dmabuf (dev, dmap);
#endif

  dmap->dmabuf = NULL;
  return 0;
}

/*ARGSUSED*/
static int
vortex_alloc_buffer (int dev, dmap_t * dmap, int direction)
{
  vortex_devc *devc = audio_engines[dev]->devc;
  oss_native_word phaddr;

  if (dmap->dmabuf != NULL)
    return 0;
#if 1
  dmap->buffsize = 4 * 4096;	/* 4 subbuffers */
  dmap->dmabuf =
    CONTIG_MALLOC (devc->osdev, dmap->buffsize, MEMLIMIT_32BITS, &phaddr);
  dmap->dmabuf_phys = phaddr;
#ifdef linux
  oss_reserve_pages ((oss_native_word) dmap->dmabuf,
		     (oss_native_word) dmap->dmabuf + 4 * 4096 - 1);
#endif
#else
  if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0)
    return err;

  /* Reserve the last page for internal use (channel buffers) */
  p = dmap->buffsize - 4096;
  dmap->buffsize = p;

  if (dmap->buffsize < (4 * 1024))
    {
      cmn_err (CE_WARN, "Allocated DMA buffer smaller than 8k\n");
      vortex_free_buffer (dev, dmap, direction);
      return -ENOSPC;
    }
#endif

  return 0;
}

/*ARGSUSED*/
static int
vortex_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  vortex_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags, dmastat = 0;
  int ptr = 0;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_OUTPUT)
    {
      dmastat = ReadReg (devc, dma_base + 0x5c0);
    }
  if (direction == PCM_ENABLE_INPUT)
    {
      dmastat = ReadReg (devc, dma_base + 0x5c8);
    }
  ptr = ((dmastat >> 12) & 0x03) * 4096 + (dmastat & 4095);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr;
}

static audiodrv_t vortex_driver = {
  vortex_open,
  vortex_close,
  vortex_output_block,
  vortex_start_input,
  vortex_ioctl,
  vortex_prepare_for_input,
  vortex_prepare_for_output,
  vortex_reset,
  NULL,
  NULL,
  vortex_reset_input,
  vortex_reset_output,
  vortex_trigger,
  vortex_set_rate,
  vortex_set_format,
  vortex_set_channels,
  NULL,
  NULL,
  NULL,				/* vortex_check_input, */
  NULL,				/* vortex_check_output, */
  vortex_alloc_buffer,
  vortex_free_buffer,
  NULL,
  NULL,
  vortex_get_buffer_pointer
};

static void
attach_channel (vortex_devc * devc, int my_mixer)
{
  int adev, i;
  int first_dev = 0;

  for (i = 0; i < MAX_PORTC; i++)
    {
      char tmp_name[100];
      vortex_portc *portc = &devc->portc[i];
      int caps =
	ADEV_FIXEDRATE | ADEV_AUTOMODE | ADEV_STEREOONLY | ADEV_16BITONLY;

      if (i == 0)
	{
	  sprintf (tmp_name, "Aureal Vortex (%s)", devc->name);
	  caps |= ADEV_DUPLEX;
	}
      else
	{
	  sprintf (tmp_name, "Aureal Vortex (%s) (shadow)", devc->name);
	  caps |= ADEV_DUPLEX | ADEV_SHADOW;
	}
      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&vortex_driver,
					sizeof (audiodrv_t),
					caps,
					AFMT_U8 | AFMT_S16_LE, NULL, -1)) < 0)
	{
	  adev = -1;
	  return;
	}
      else
	{
	  if (i == 0)
	    first_dev = adev;
	  audio_engines[adev]->devc = devc;
	  audio_engines[adev]->portc = portc;
	  audio_engines[adev]->vmix_flags = VMIX_MULTIFRAG;
	  audio_engines[adev]->rate_source = first_dev;
	  audio_engines[adev]->fixed_rate = 48000;
	  audio_engines[adev]->min_rate = 48000;
	  audio_engines[adev]->max_rate = 48000;
#if 0
	  audio_engines[adev]->min_block = 4096;
	  audio_engines[adev]->max_block = 4096;
#endif
	  audio_engines[adev]->mixer_dev = my_mixer;
	  portc->voice_chn = 0;
	  portc->open_mode = 0;
	  portc->audiodev = adev;
	  portc->audio_enabled = 0;
	}
    }
  return;
}

#ifdef USE_SRC
static void
init_src (vortex_devc * devc)
{
  int i;

  for (i = 0; i < 0x800; i += 4)
    WriteReg (devc, src_input_fifo + i, 0);
  for (i = 0; i < 0x080; i += 4)
    WriteReg (devc, src_output_double_buffer + i, 0);
  for (i = 0; i < 0x040; i += 4)
    WriteReg (devc, src_next_channel + i, 0);
  for (i = 0; i < 0x054; i += 4)
    WriteReg (devc, src_sr_header + i, 0);

  WriteReg (devc, src_active_sample_rate, 0);
  WriteReg (devc, src_throttle_source, 0);
  WriteReg (devc, src_throttle_count_size, 511);

  for (i = 0; i < 0x1bf; i += 4)
    WriteReg (devc, src_channel_params + i, 0);
}
#endif

int init_vortex2 (vortex_devc * devc, int is_mx300);
int vortex2intr (oss_device_t * osdev);

static int
init_vortex (vortex_devc * devc)
{
  int my_mixer;
  int i;

/*
 * Reset Vortex
 */
  WriteReg (devc, GCTL, 0xffffffff);
  oss_udelay (1000);

  InitCodec (devc);
  SetupCodec (devc);
  InitAdb (devc);
  EnableCodecChannel (devc, 0);
  EnableCodecChannel (devc, 1);
  init_fifos (devc);
  EnableAdbWtd (devc, 17);
  SetupRoutes (devc);
#ifdef USE_SRC
  init_src (devc);
#endif

  /*
   * DMA controller memory is supposed to contain 0xdeadbeef after
   * reset.
   */
  if (ReadReg (devc, 0x10000 + 0x7c0) != 0xdeadbeef)
    cmn_err (CE_WARN,
	     "DMA memory check returned unexpected result %08x\n",
	     ReadReg (devc, 0x10000 + 0x7c0));

  my_mixer = ac97_install (&devc->ac97devc, "AC97 Mixer", ac97_read,
			   ac97_write, devc, devc->osdev);
  if (my_mixer >= 0)
    devc->mixer_dev = my_mixer;
  else
    return 0;

  WriteReg (devc, GCTL, ReadReg (devc, GCTL) | GIRQEN);	/* Enable IRQ */
  WriteReg (devc, CODSMPLTMR, 0x0);
  WriteReg (devc, GIRQCTL, DMAENDIRQST | DMABERRST | TIMIRQST);

  for (i = 0; i < 2; i++)
    devc->dst_routed[i] = 0;

  /* Turn on vortex serial interface outputs to codec */
  EnableCodecChannel (devc, 0);
  EnableCodecChannel (devc, 1);

  /* Zero Out the FIFO Pointers */
  for (i = 0; i < 48; i++)
    {
      WriteReg (devc, fifo_base + 0x1800 + (4 * i), 0x00000020);
    }
  for (i = 2; i < 6; i++)
    {
      WriteReg (devc, fifo_base + 0x1800 + (4 * i), 0x00000000);
    }
  WriteReg (devc, fifo_base + 0x18c0, 0x00000843);
  /* Clear out FIFO data */
  for (i = 0; i < 4; i++)
    ClearDataFifo (devc, i);

  /* Set up the DMA engine to grab DMA memory */
  WriteReg (devc, dma_base + 0x61c, 0);	/* Clear Dma Status0 */
  WriteReg (devc, dma_base + 0x620, 0);	/* Clear Dma Status1 */
  WriteReg (devc, dma_base + 0x624, 0);	/* Clear Dma Status2 */

  attach_channel (devc, my_mixer);

  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "VORTEX",
					"Aureal Vortex UART",
					&vortex_midi_driver,
					sizeof (midi_driver_t),
					/* &std_midi_synth, */ NULL,
					0, devc, devc->osdev);
  devc->midi_opened = 0;
  vortex_midi_init (devc);

  return 1;
}

int
vortex_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision /*, pci_latency */ ;
  unsigned short pci_command, vendor, device;
  unsigned short subsystemid, subvendor;
  int is_mx300 = 0;
  vortex_devc *devc;

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

  oss_pci_byteswap (osdev, 1);

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

  if (vendor != AUREAL_VENDOR_ID || (device != AUREAL_VORTEX &&
				     device != AUREAL_VORTEX2))
    return 0;

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

  pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &subsystemid);
  pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &subvendor);

  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;

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

  if (device == AUREAL_VORTEX2)
    {
      devc->name = "Aureal Vortex AU8830";
      devc->id = AUREAL_VORTEX2;
      devc->bar0_size = 256 * 1024;
    }
  else
    {
      devc->name = "Aureal Vortex AU8820";
      devc->id = AUREAL_VORTEX;
      devc->bar0_size = 128 * 1024;
    }

  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
  pci_command &= ~(PCI_COMMAND_SERR | PCI_COMMAND_PARITY);
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  if (subvendor == 0x1092 && subsystemid == 0x3001)
    is_mx300 = 1;

  /* Prgram IORDY for VIA chipset failure */
  pci_write_config_byte (osdev, 0x40, 0xff);

  /* Map the shared memory area */
  devc->bar0virt =
    (unsigned int *) MAP_PCI_MEM (devc->osdev, 0, devc->bar0addr,
				  devc->bar0_size);
  devc->dwRegister = devc->bar0virt;

  /* Disable all interrupts */
  WriteReg (devc, GIRQCTL, 0x00000000);

  oss_register_device (osdev, devc->name);

  if (devc->id == AUREAL_VORTEX)
    if (oss_register_interrupts (devc->osdev, 0, vortexintr, NULL) < 0)
      {
	cmn_err (CE_WARN, "Can't allocate IRQ%d\n", pci_irq_line);
	return 0;
      }

  if (devc->id == AUREAL_VORTEX2)
    if (oss_register_interrupts (devc->osdev, 0, vortex2intr, NULL) < 0)
      {
	cmn_err (CE_WARN, "Can't allocate IRQ%d\n", pci_irq_line);
	return 0;
      }


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

  if (devc->id == AUREAL_VORTEX2)
    return init_vortex2 (devc, is_mx300);
  else
    return init_vortex (devc);	/* Detected */
}


extern void unload_vortex2 (oss_device_t * osdev);

int
vortex_detach (oss_device_t * osdev)
{
  vortex_devc *devc = (vortex_devc *) osdev->devc;

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

  if (devc->id == AUREAL_VORTEX2)
    unload_vortex2 (osdev);
  else
    {
      DisableCodecChannel (devc, 0);
      DisableCodecChannel (devc, 1);
      DisableAdbWtd (devc, 17);

      /* Disable routes */
      del_route (devc, codec_chan0_dst_addr, 0);
      del_route (devc, codec_chan1_dst_addr, 0);
      del_route (devc, fifo_chan2_dst_addr, 0);
      del_route (devc, fifo_chan3_dst_addr, 0);
      del_route (devc, fifo_chan2a_dst_addr, 0);
      del_route (devc, fifo_chan3a_dst_addr, 0);

      /* Disable all interrupts */
      WriteReg (devc, GIRQCTL, 0x00000000);
    }

  oss_unregister_interrupts (devc->osdev);

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

  UNMAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, devc->bar0virt,
		 devc->bar0_size);

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