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


#include "oss_cs461x_cfg.h"
#include "midi_core.h"
#include "ac97.h"
#include "oss_pci.h"
#include "cs461x.h"

extern int cs461x_clkrun_fix;

#define CRYSTAL_VENDOR_ID	0x1013
#define CRYSTAL_CS4610_ID	0x6001
#define CRYSTAL_CS461x_ID	0x6003
#define CRYSTAL_CS4615_ID	0x6004

#define USE_SG

#define WRITEB(a,d) devc->bRegister0[a] = d
#define READB(a) devc->bRegister0[a]
#define WRITEW(a,d) devc->wRegister0[a>>1] = d
#define READW(a) devc->wRegister0[a>>1]

#define READ0L(a) (devc->dwRegister0[a>>2])
#define WRITE0L(a, d) (devc->dwRegister0[a>>2] = d)
#define READ1L(a) (devc->dwRegister1[a>>2])
#define WRITE1L(a,d) (devc->dwRegister1[a>>2] = d)

#define READ1L(a) (devc->dwRegister1[a>>2])

#ifdef OSS_BIG_ENDIAN
static unsigned int
ymf_swap (unsigned int x)
{
  return ((x & 0x000000ff) << 24) |
    ((x & 0x0000ff00) << 8) |
    ((x & 0x00ff0000) >> 8) | ((x & 0xff000000) >> 24);
}

#define LSWAP(x) ymf_swap(x)
#else
#define LSWAP(x) 	x
#endif

#define MAX_PORTC 2

typedef struct cs461x_portc
{
  int speed, bits, channels;
  int open_mode;
  int audio_enabled;
  int trigger_bits;
  int audiodev;
}
cs461x_portc;

typedef struct cs461x_devc
{
  oss_device_t *osdev;
  char *chip_name;
  unsigned short subsysid;

  oss_native_word bar0addr, bar1addr;
  unsigned int *bar0virt, *bar1virt;
  volatile unsigned int *dwRegister0, *dwRegister1;
  volatile unsigned short *wRegister0, *wRegister1;
  volatile unsigned char *bRegister0, *bRegister1;
  int irq;
  int dual_codec;
  unsigned int *play_sgbuf;
  unsigned int play_sgbuf_phys;
  oss_dma_handle_t play_sgbuf_dma_handle;

  /* Mutex */
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* MIDI */
  int midi_opened;
  int midi_dev;
  oss_midi_inputbyte_t midi_input_intr;

  /* Mixer parameters */
  ac97_devc ac97devc, ac97devc2;
  int mixer_dev, mixer2_dev;

  /* Audio parameters */
  cs461x_portc portc[MAX_PORTC];
  int open_mode;
  oss_native_word PCTL;		/*Play Control Register */
  oss_native_word CCTL;		/*Record Control Register */
  int processor_started;
}
cs461x_devc;

#define MAX_CS461x 6

static int ac97_write (void *devc, int addr, int data);
static int ac97_read (void *devc, int addr);

static void
PokeBA0 (cs461x_devc * devc, unsigned int offset, unsigned int value)
{
  WRITE0L (offset, value);
}

static unsigned int
PeekBA0 (cs461x_devc * devc, unsigned int offset)
{
  unsigned int value;

  value = READ0L (offset);
  return (value);

}

static int
ac97_read (void *devc_, int offset)
{
  cs461x_devc *devc = devc_;
  int count;
  unsigned int status, value;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  /*
   * Make sure that there is not data sitting around from a previous
   * uncompleted access. ACSDA = Status Data Register = 47Ch
   */
  status = PeekBA0 (devc, BA0_ACSDA);
  /* Get the actual AC97 register from the offset */
  PokeBA0 (devc, BA0_ACCAD, offset - BA0_AC97_RESET);
  PokeBA0 (devc, BA0_ACCDA, 0);
  PokeBA0 (devc, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | ACCTL_VFRM |
	   ACCTL_ESYN | ACCTL_RSTN);

  /* Wait for the read to occur. */
  for (count = 0; count < 10; count++)
    {
      /* First, we want to wait for a short time. */
      oss_udelay (100);
      /*
       * Now, check to see if the read has completed.
       * ACCTL = 460h, DCV should be reset by now and 460h = 17h
       */
      status = PeekBA0 (devc, BA0_ACCTL);
      if (!(status & ACCTL_DCV))
	{
	  break;
	}
    }

  /* Make sure the read completed. */
  if (status & ACCTL_DCV)
    {
      cmn_err (CE_WARN, "AC97 Read Timedout\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }

  /* Wait for the valid status bit to go active. */

  for (count = 0; count < 10; count++)
    {
      /*
       * Read the AC97 status register.
       * ACSTS = Status Register = 464h
       */
      status = PeekBA0 (devc, BA0_ACSTS);
      /*
       * See if we have valid status.
       * VSTS - Valid Status
       */
      if (status & ACSTS_VSTS)
	break;

      /*
       * Wait for a short while.
       */
      oss_udelay (100);
    }
  /* Make sure we got valid status. */
  if (!(status & ACSTS_VSTS))
    {
      cmn_err (CE_WARN, "AC97 Read Timedout(2)\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }

  /*
   * Read the data returned from the AC97 register.
   * ACSDA = Status Data Register = 474h
   */
  value = PeekBA0 (devc, BA0_ACSDA);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return (value);
}

static int
ac97_write (void *devc_, int offset, int data)
{
  cs461x_devc *devc = devc_;
  int count;
  unsigned int status = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  PokeBA0 (devc, BA0_ACCAD, offset);
  PokeBA0 (devc, BA0_ACCDA, data);
  PokeBA0 (devc, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);
  for (count = 0; count < 10; count++)
    {
      /* First, we want to wait for a short time. */
      oss_udelay (100);
      /* Now, check to see if the write has completed. */
      /* ACCTL = 460h, DCV should be reset by now and 460h = 07h */
      status = PeekBA0 (devc, BA0_ACCTL);
      if (!(status & ACCTL_DCV))
	break;
    }

  /* write didn't completed. */
  if (status & ACCTL_DCV)
    {
      cmn_err (CE_WARN, "AC97 Write timeout\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}

static int
ac97_read2 (void *devc_, int offset)
{
  cs461x_devc *devc = devc_;
  int count;
  unsigned int status, value;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  /*
   * Make sure that there is not data sitting around from a previous
   * uncompleted access. ACSDA = Status Data Register = 47Ch
   */
  status = PeekBA0 (devc, BA0_ACSDA2);

  /* Get the actual AC97 register from the offset */
  PokeBA0 (devc, BA0_ACCAD, offset);
  PokeBA0 (devc, BA0_ACCDA, 0);
  PokeBA0 (devc, BA0_ACCTL,
	   ACCTL_DCV | ACCTL_TC | ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN |
	   ACCTL_RSTN);


  /* Wait for the read to occur. */
  for (count = 0; count < 10; count++)
    {
      /* First, we want to wait for a short time. */
      oss_udelay (1000);
      /*
       * Now, check to see if the read has completed.
       * ACCTL = 460h, DCV should be reset by now and 460h = 17h
       */
      status = PeekBA0 (devc, BA0_ACCTL);
      if (!(status & ACCTL_DCV))
	break;
    }

  /* Make sure the read completed. */
  if (PeekBA0 (devc, BA0_ACCTL) & ACCTL_DCV)
    {
      cmn_err (CE_WARN, "Secondary AC97 Read Timedout\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }

  /* Wait for the valid status bit to go active. */

  for (count = 0; count < 10; count++)
    {
      /*
       * Read the AC97 status register.
       * ACSTS = Status Register = 464h
       */
      status = PeekBA0 (devc, BA0_ACSTS2);
      /*
       * See if we have valid status.
       * VSTS - Valid Status
       */
      if (status & ACSTS_VSTS)
	break;

      /*
       * Wait for a short while.
       */
      oss_udelay (1000);
    }
  /* Make sure we got valid status */
  if (!(status & ACSTS_VSTS))
    {
      cmn_err (CE_WARN, "Secondary AC97 Read Timedout(2)\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }

  /*
   * Read the data returned from the AC97 register.
   * ACSDA = Status Data Register = 474h
   */
  value = PeekBA0 (devc, BA0_ACSDA2);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return (value);
}

static int
ac97_write2 (void *devc_, int offset, int data)
{
  cs461x_devc *devc = devc_;
  int count;
  unsigned int status = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  PokeBA0 (devc, BA0_ACCAD, offset);
  PokeBA0 (devc, BA0_ACCDA, data);
  PokeBA0 (devc, BA0_ACCTL,
	   ACCTL_DCV | ACCTL_TC | ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);
  for (count = 0; count < 10; count++)
    {
      /* First, we want to wait for a short time. */
      oss_udelay (1000);
      /* Now, check to see if the write has completed. */
      /* ACCTL = 460h, DCV should be reset by now and 460h = 07h */
      status = PeekBA0 (devc, BA0_ACCTL);
      if (!(status & ACCTL_DCV))
	break;
    }

  /* write didn't completed. */
  if (status & ACCTL_DCV)
    {
      cmn_err (CE_WARN, "Secondary AC97 Write timeout\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return (-1);
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}

/*  
 * this is 3*1024 for parameter, 3.5*1024 for sample and 2*3.5*1024 
 * for code since each instruction is 40 bits and takes two dwords
 */
#define INKY_BA1_DWORD_SIZE (13 * 1024 + 512)
/* this is parameter, sample, and code */
#define INKY_MEMORY_COUNT 3
struct cs461x_firmware_struct
{
  struct
  {
    unsigned int ulDestAddr;
    unsigned int ulSourceSize;
  }
  MemoryStat[INKY_MEMORY_COUNT];
  unsigned int BA1Array[INKY_BA1_DWORD_SIZE];
};
#include "cs461x_dsp.h"

static void
DoTransfer (cs461x_devc * devc, unsigned int *fpulSrc,
	    unsigned int ulByteDestOffset, unsigned int ulByteLength)
{
  int dwByteCounter;

  if (ulByteDestOffset & 0x3)
    {
      cmn_err (CE_WARN, "invalid DMA address\n");
      return;
    }

  for (dwByteCounter = 0; dwByteCounter < ulByteLength; dwByteCounter += 4)
    {
      WRITE1L ((ulByteDestOffset + dwByteCounter),
	       fpulSrc[dwByteCounter / 4]);
    }
}

static void
install_ucode (cs461x_devc * devc)
{
  unsigned int i, count;

  count = 0;
  for (i = 0; i < INKY_MEMORY_COUNT; i++)
    {

      DoTransfer (devc, (unsigned int *) (cs461x_firmware.BA1Array + count),
		  cs461x_firmware.MemoryStat[i].ulDestAddr,
		  cs461x_firmware.MemoryStat[i].ulSourceSize);
      count += cs461x_firmware.MemoryStat[i].ulSourceSize / 4;
    }
}

void
clear_serial_fifo (cs461x_devc * devc)
{
  unsigned int ulIdx, ulLoop;
  unsigned int ulStatus = 0;
  unsigned int ulCLKCR1;


  ulCLKCR1 = PeekBA0 (devc, BA0_CLKCR1);

  if (!(ulCLKCR1 & CLKCR1_SWCE))
    {
      PokeBA0 (devc, BA0_CLKCR1, ulCLKCR1 | CLKCR1_SWCE);
    }
  PokeBA0 (devc, BA0_SERBWP, 0);

  for (ulIdx = 0; ulIdx < 256; ulIdx++)
    {
      /*
       * Make sure the previous FIFO write operation has completed.
       */
      for (ulLoop = 0; ulLoop < 5; ulLoop++)
	{
	  oss_udelay (100);
	  ulStatus = PeekBA0 (devc, BA0_SERBST);
	  if (!(ulStatus & SERBST_WBSY))
	    {
	      break;
	    }
	}

      if (ulStatus & SERBST_WBSY)
	{
	  if (!(ulCLKCR1 & CLKCR1_SWCE))
	    {
	      PokeBA0 (devc, BA0_CLKCR1, ulCLKCR1);
	    }
	}

      /* Write the serial port FIFO index. */

      PokeBA0 (devc, BA0_SERBAD, ulIdx);

      /*
       * Tell the serial port to load the new value into the FIFO location.
       */
      PokeBA0 (devc, BA0_SERBCM, SERBCM_WRC);
    }

  /*
   * Now, if we powered up the devices, then power them back down again.
   * This is kinda ugly, but should never happen.
   */
  if (!(ulCLKCR1 & CLKCR1_SWCE))
    {
      PokeBA0 (devc, BA0_CLKCR1, ulCLKCR1);
    }

}

static int
cs461x_reset_processor (cs461x_devc * devc)
{
  unsigned int ulIdx;

  /* Write the reset bit of the SP control register. */
  WRITE1L (BA1_SPCR, SPCR_RSTSP);
  WRITE1L (BA1_SPCR, SPCR_DRQEN);

  /* Clear the trap registers. */
  for (ulIdx = 0; ulIdx < 8; ulIdx++)
    {
      WRITE1L (BA1_DREG, DREG_REGID_TRAP_SELECT + ulIdx);
      WRITE1L (BA1_TWPR, 0xFFFF);
    }

  WRITE1L (BA1_DREG, 0);
  WRITE1L (BA1_FRMT, 0xadf);
  return (0);
}

static int
cs461x_start_processor (cs461x_devc * devc)
{

  unsigned int ulCount;
  int ulTemp = 0;

  /* reset the processor first */
  cs461x_reset_processor (devc);

  /* reload the ucode */
  install_ucode (devc);

  /* Set the frame timer to reflect the number of cycles per frame. */
  WRITE1L (BA1_FRMT, 0xADF);

  /*
   * Turn on the run, run at frame, and DMA enable bits in the local copy of
   * the SP control register.
   */
  WRITE1L (BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN);

  /*
   * Wait until the run at frame bit resets itself in the SP control
   * register.
   */
  for (ulCount = 0; ulCount < 25; ulCount++)
    {
      /* Wait a little bit, so we don't issue PCI reads too frequently. */
      oss_udelay (100);

      /* Fetch the current value of the SP status register. */
      ulTemp = READ1L (BA1_SPCR);

      /* If the run at frame bit has reset, then stop waiting. */
      if (!(ulTemp & SPCR_RUNFR))
	break;
    }
  /* If the run at frame bit never reset, then return an error. */
  if (ulTemp & SPCR_RUNFR)
    {
      cmn_err (CE_WARN, "Start(): SPCR_RUNFR never reset.\n");
    }
  devc->processor_started = 1;
  return 0;
}

static int
cs461xintr (oss_device_t * osdev)
{
  cs461x_devc *devc = (cs461x_devc *) osdev->devc;
  cs461x_portc *portc;
  unsigned int status;
  int i;
  unsigned int uart_stat;
  int serviced = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

/* Read the Interrupt Status Register */
  status = PeekBA0 (devc, BA0_HISR);

/*
 * This is the MIDI read interrupt service. First check to see
 * if the MIDI interrupt flag is set in the HISR register. Next
 * read the MIDI status register. See if Receive Buffer Empty 
 * is empty (0=FIFO Not empty, 1=FIFO is empty
 */
  if ((devc->midi_opened & OPEN_READ) && (status & HISR_MIDI))
    {
      uart_stat = PeekBA0 (devc, BA0_MIDSR);
/*
 * read one byte of MIDI data and hand it off the the sequencer module
 * to decode this. Keep checking to see if the data is available. Stop
 * when no more data is there in the FIFO.
 */
      while (!(uart_stat & MIDSR_RBE))
	{
	  unsigned char d;
	  d = PeekBA0 (devc, BA0_MIDRP);

	  if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
	    devc->midi_input_intr (devc->midi_dev, d);
	  uart_stat = PeekBA0 (devc, BA0_MIDSR);
	}
    }

/* Audio interrupt handling */
  if (status & 0x3)
    {
      for (i = 0; i < MAX_PORTC; i++)
	{
	  portc = &devc->portc[i];
	  if (status & 0x1)
	    if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	      {
		dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
		int ptr, n;

		ptr = READ1L (BA1_PBA) - dmap->dmabuf_phys;
		ptr = 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, 0);
	      }

	  if (status & 0x2)
	    if (portc->trigger_bits & PCM_ENABLE_INPUT)
	      {

		dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in;
		int ptr, n;

		ptr = READ1L (BA1_CBA) - dmap->dmabuf_phys;
		ptr = 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);
	      }
	}
    }

  serviced = 1;
  PokeBA0 (devc, BA0_HICR, 0x03);

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return serviced;
}

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

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

  if (audio_engines[dev]->flags & ADEV_FIXEDRATE)
    {
      audio_engines[dev]->fixed_rate = 48000;
      arg = 48000;
    }
  else
    audio_engines[dev]->fixed_rate = 0;

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

static short
cs461x_audio_set_channels (int dev, short arg)
{
  cs461x_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
cs461x_audio_set_format (int dev, unsigned int arg)
{
  cs461x_portc *portc = audio_engines[dev]->portc;

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

  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
cs461x_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return OSS_EINVAL;
}

static void cs461x_audio_trigger (int dev, int state);

static void
cs461x_audio_reset (int dev)
{
  cs461x_audio_trigger (dev, 0);
}

static void
cs461x_audio_reset_input (int dev)
{
  cs461x_portc *portc = audio_engines[dev]->portc;
  cs461x_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
cs461x_audio_reset_output (int dev)
{
  cs461x_portc *portc = audio_engines[dev]->portc;
  cs461x_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
cs461x_audio_open (int dev, int mode, int open_flags)
{
  cs461x_portc *portc = audio_engines[dev]->portc;
  cs461x_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (portc->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }

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

  devc->open_mode |= mode;

  portc->open_mode = mode;
  portc->audio_enabled &= ~mode;
#if 0
  if (mode & OPEN_READ)
    {
      dmapin->buffsize = 4096;
      audio_engines[dev]->fixed_rate = 48000;
      audio_engines[dev]->min_rate = 48000;
      audio_engines[dev]->max_rate = 48000;
      audio_engines[dev]->flags |=
	ADEV_FIXEDRATE | ADEV_16BITONLY | ADEV_STEREOONLY;
    }
  if (mode & OPEN_WRITE)
    {
      audio_engines[dev]->min_rate = 5000;
      audio_engines[dev]->max_rate = 48000;
      audio_engines[dev]->flags &=
	~(ADEV_FIXEDRATE | ADEV_16BITONLY | ADEV_STEREOONLY);
      audio_engines[dev]->fixed_rate = 0;
    }
#endif
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static void
cs461x_audio_close (int dev, int mode)
{
  cs461x_portc *portc = audio_engines[dev]->portc;
  cs461x_devc *devc = audio_engines[dev]->devc;

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

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

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

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

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

static void
cs461x_audio_trigger (int dev, int state)
{
  oss_native_word flags, tmp;
  cs461x_devc *devc = audio_engines[dev]->devc;
  cs461x_portc *portc = audio_engines[dev]->portc;

  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))
	    {
	      tmp = READ1L (BA1_PCTL);
	      tmp &= 0xFFFF;
	      tmp |= devc->PCTL;
	      WRITE1L (BA1_PCTL, tmp);
	      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;
	      tmp = READ1L (BA1_PCTL);
	      tmp &= 0xFFFF;
	      WRITE1L (BA1_PCTL, tmp);
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      tmp = READ1L (BA1_CCTL);
	      tmp &= 0xFFFF0000;
	      tmp |= devc->CCTL;
	      WRITE1L (BA1_CCTL, tmp);
	      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;
	      tmp = READ1L (BA1_CCTL);
	      tmp &= 0xFFFF0000;
	      WRITE1L (BA1_CCTL, tmp);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static int
cs461x_play_rate (cs461x_devc * devc, unsigned int ulInRate)
{
/*define GOF_PER_SEC 200*/
  unsigned int ulTemp1, ulTemp2;
  unsigned int ulPhiIncr;
  unsigned int ulCorrectionPerGOF, ulCorrectionPerSec;
  unsigned int ulOutRate = 48000;

  ulTemp1 = ulInRate << 16;
  ulPhiIncr = ulTemp1 / ulOutRate;
  ulTemp1 -= ulPhiIncr * ulOutRate;
  ulTemp1 <<= 10;
  ulPhiIncr <<= 10;
  ulTemp2 = ulTemp1 / ulOutRate;
  ulPhiIncr += ulTemp2;
  ulTemp1 -= ulTemp2 * ulOutRate;
  ulCorrectionPerGOF = ulTemp1 / 200;
  ulTemp1 -= ulCorrectionPerGOF * 200;
  ulCorrectionPerSec = ulTemp1;

  /* Fill in the SampleRateConverter control block. */
  WRITE1L (BA1_PSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) |
	   (ulCorrectionPerGOF & 0xFFFF));
  WRITE1L (BA1_PPI, ulPhiIncr);
  return 0;
}

static int
cs461x_record_rate (cs461x_devc * devc, int ulOutRate)
{
  unsigned int ulPhiIncr, ulCoeffIncr, ulTemp1, ulTemp2;
  unsigned int ulCorrectionPerGOF, ulCorrectionPerSec, ulInitialDelay;
  unsigned int ulInRate = 48000;

  /*
   * We can only decimate by up to a factor of 1/9th the hardware rate.
   * Return an error if an attempt is made to stray outside that limit.
   */
  if ((ulOutRate * 9) < ulInRate)
    {
      cmn_err (CE_WARN,
	       "SetCaptureSampleRate(): Requested output rate is below 1/9th of the input rate.\n");
      return (-1);
    }

  /*
   * We can not capture at at rate greater than the Input Rate (48000).
   * Return an error if an attempt is made to stray outside that limit.
   */
  if (ulOutRate > ulInRate)
    {
      cmn_err (CE_WARN,
	       "SetCaptureSampleRate(): Requested output rate is greater than the input rate.");
      return (-1);
    }
  ulTemp1 = ulOutRate << 16;
  ulCoeffIncr = ulTemp1 / ulInRate;
  ulTemp1 -= ulCoeffIncr * ulInRate;
  ulTemp1 <<= 7;
  ulCoeffIncr <<= 7;
  ulCoeffIncr += ulTemp1 / ulInRate;
  ulCoeffIncr ^= 0xFFFFFFFF;
  ulCoeffIncr++;
  ulTemp1 = ulInRate << 16;
  ulPhiIncr = ulTemp1 / ulOutRate;
  ulTemp1 -= ulPhiIncr * ulOutRate;
  ulTemp1 <<= 10;
  ulPhiIncr <<= 10;
  ulTemp2 = ulTemp1 / ulOutRate;
  ulPhiIncr += ulTemp2;
  ulTemp1 -= ulTemp2 * ulOutRate;
  ulCorrectionPerGOF = ulTemp1 / 200;
  ulTemp1 -= ulCorrectionPerGOF * 200;
  ulCorrectionPerSec = ulTemp1;
  ulInitialDelay = ((ulInRate * 24) + ulOutRate - 1) / ulOutRate;
  /* Fill in the VariDecimate control block. */
  WRITE1L (BA1_CSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) |
	   (ulCorrectionPerGOF & 0xFFFF));
  WRITE1L (BA1_CCI, ulCoeffIncr);
  WRITE1L (BA1_CD,
	   (((BA1_VARIDEC_BUF_1 + (ulInitialDelay << 2)) << 16) & 0xFFFF0000)
	   | 0x80);
  WRITE1L (BA1_CPI, ulPhiIncr);
#if 0
  {
    unsigned int frameGroupLength, cnt;
    int rate = ulOutRate;

    frameGroupLength = 1;
    for (cnt = 2; cnt <= 64; cnt *= 2)
      {
	if (((rate / cnt) * cnt) != rate)
	  frameGroupLength *= 2;
      }
    if (((rate / 3) * 3) != rate)
      {
	frameGroupLength *= 3;
      }
    for (cnt = 5; cnt <= 125; cnt *= 5)
      {
	if (((rate / cnt) * cnt) != rate)
	  frameGroupLength *= 5;
      }

    WRITE1L (BA1_CFG1, frameGroupLength);
    WRITE1L (BA1_CFG2, (0x00800000 | frameGroupLength));
    WRITE1L (BA1_CCST, 0x0000FFFF);
    WRITE1L (BA1_CSPB, ((65536 * rate) / 24000));
    WRITE1L ((BA1_CSPB + 4), 0x0000FFFF);
  }
#endif
  return (0);
}

struct InitStruct
{
  unsigned long off;
  unsigned long val;
}
InitArray[] =
{
  {
  0x00000040, 0x3fc0000f}
  ,
  {
  0x0000004c, 0x04800000}
  ,
  {
  0x000000b3, 0x00000780}
  ,
  {
  0x000000b7, 0x00000000}
  ,
  {
  0x000000bc, 0x07800000}
  ,
  {
  0x000000cd, 0x00800000}
,};


/*
 * "SetCaptureSPValues()" -- Initialize record task values before each
 *      capture startup.
 */
void
SetCaptureSPValues (cs461x_devc * devc)
{
  unsigned i, offset;
  for (i = 0; i < sizeof (InitArray) / sizeof (struct InitStruct); i++)
    {
      offset = InitArray[i].off * 4;	/* 8bit to 32bit offset value */
      WRITE1L (offset, InitArray[i].val);
    }
}

/*ARGSUSED*/
static int
cs461x_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  cs461x_devc *devc = audio_engines[dev]->devc;
  cs461x_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  /* Start the processor */
  if (!devc->processor_started)
    cs461x_start_processor (devc);

  /* Set Capture S/P values */
  SetCaptureSPValues (devc);
  /* set the record rate */
  cs461x_record_rate (devc, portc->speed);

  /* write the buffer address */
  WRITE1L (BA1_CBA, dmap->dmabuf_phys);

  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

/*ARGSUSED*/
static int
cs461x_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  cs461x_devc *devc = audio_engines[dev]->devc;
  cs461x_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  /* Start the processor */
  if (!devc->processor_started)
    cs461x_start_processor (devc);

#ifdef USE_SG
  {
    unsigned int sg_temp[9], sg_npages = 0;
    int i;
    oss_native_word Count, playFormat, tmp, tmp2;

    sg_npages = (dmap->bytes_in_use) / 4096;

    devc->play_sgbuf[0] = dmap->dmabuf_phys;
    devc->play_sgbuf[1] = 0x00000008;

    for (i = 0; i < sg_npages; i++)
      {
	devc->play_sgbuf[2 * i] = dmap->dmabuf_phys + 4096 * i;
	if (i == sg_npages - 1)
	  tmp2 = 0xbfff0000;
	else
	  tmp2 = 0x80000000 + 8 * (i + 1);

	devc->play_sgbuf[2 * i + 1] = tmp2;
      }
    sg_temp[0] = 0x82c0200d;
    sg_temp[1] = 0xffff0000;
    sg_temp[2] = devc->play_sgbuf[0];
    sg_temp[3] = 0x00010600;
    sg_temp[4] = devc->play_sgbuf[2];
    sg_temp[5] = 0x80000010;
    sg_temp[6] = devc->play_sgbuf[0];
    sg_temp[7] = devc->play_sgbuf[2];
    sg_temp[8] = (devc->play_sgbuf_phys & 0xffff000) | 0x10;

    for (i = 0; i < sizeof (sg_temp) / 4; i++)
      WRITE1L ((BA1_PDTC + i * 4), sg_temp[i]);

    WRITE1L (BA1_PBA, dmap->dmabuf_phys);

    cs461x_play_rate (devc, (unsigned int) portc->speed);

    Count = 4;
    playFormat = READ1L (BA1_PFIE);
    playFormat &= ~0x0000f03f;

    if ((portc->channels == 2))
      {
	playFormat &= ~DMA_RQ_C2_AC_MONO_TO_STEREO;
	Count *= 2;
      }
    else
      playFormat |= DMA_RQ_C2_AC_MONO_TO_STEREO;

    if ((portc->bits == 16))
      {
	playFormat &=
	  ~(DMA_RQ_C2_AC_8_TO_16_BIT | DMA_RQ_C2_AC_SIGNED_CONVERT);
	Count *= 2;
      }
    else
      playFormat |= (DMA_RQ_C2_AC_8_TO_16_BIT | DMA_RQ_C2_AC_SIGNED_CONVERT);

    WRITE1L (BA1_PFIE, playFormat);

    tmp = READ1L (BA1_PDTC);
    tmp &= 0xfffffe00;
    WRITE1L (BA1_PDTC, tmp | --Count);
  }
#else
  {
    unsigned int pdtc_value, pfie_value;
    /* Set the sample rate converter */
    cs461x_play_rate (devc, (unsigned int) portc->speed);

    pfie_value = READ1L (BA1_PFIE);
    pfie_value &= ~0x0000f03f;

    pdtc_value = READ1L (BA1_PDTC);
    pdtc_value &= ~0x000003ff;

    /* Now set the sample size/stereo/mono */
    if ((portc->bits == 8) && (portc->channels == 1))
      {
	pdtc_value |= 0x03;
	pfie_value |= 0xB000;	/*8bit mono */
      }
    if ((portc->bits == 8) && (portc->channels == 2))
      {
	pdtc_value |= 0x07;
	pfie_value |= 0xA000;	/*8bit stereo */
      }
    if ((portc->bits == 16) && (portc->channels == 1))
      {
	pdtc_value |= 0x07;
	pfie_value |= 0x2000;	/*16bit mono */
      }
    if ((portc->bits == 16) && (portc->channels == 2))
      {
	pdtc_value |= 0x0F;
	pfie_value |= 0x0000;	/*16bit stereo */
      }

    WRITE1L (BA1_PDTC, pdtc_value);
    WRITE1L (BA1_PFIE, pfie_value);
    WRITE1L (BA1_PBA, dmap->dmabuf_phys);
  }
#endif

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

static int
cs461x_alloc_buffer (int dev, dmap_t * dmap, int direction)
{

#ifdef USE_SG
  int err;
#else
  oss_native_word phaddr;
  cs461x_devc *devc = audio_engines[dev]->devc;
#endif

  if (dmap->dmabuf != NULL)
    return 0;

#ifdef USE_SG
  if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0)
    {
      cmn_err (CE_WARN, "Failed to allocate a DMA buffer\n");
      return err;
    }
#else
  dmap->dmabuf =
    (void *) CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, &phaddr, dmap->dmabuf_dma_handle);
  dmap->dmabuf_phys = phaddr;
# ifdef linux
  oss_reserve_pages (dmap->dmabuf, dmap->dmabuf + 4096 - 1);
# endif
  dmap->buffsize = 4096;
#endif
  return 0;
}

/*ARGSUSED*/
static int
cs461x_free_buffer (int dev, dmap_t * dmap, int direction)
{
#ifndef USE_SG
  cs461x_devc *devc = audio_engines[dev]->devc;
#endif
#ifdef USE_SG
  oss_free_dmabuf (dev, dmap);
  dmap->dmabuf = 0;
#else
  if (dmap->dmabuf == NULL)
    return 0;
  CONTIG_FREE (devc->osdev, dmap->dmabuf, 4096, dmap->dmabuf_dma_handle);
# ifdef linux
  oss_unreserve_pages (dmap->dmabuf, dmap->dmabuf + 4096 - 1);
# endif
  dmap->dmabuf = 0;
#endif
  return 0;

}

static int
cs461x_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  cs461x_devc *devc = audio_engines[dev]->devc;
  unsigned int ptr = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_OUTPUT)
    {
      ptr = READ1L (BA1_PBA) - dmap->dmabuf_phys;
    }
  if (direction == PCM_ENABLE_INPUT)
    {
      ptr = READ1L (BA1_CBA) - dmap->dmabuf_phys;
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr;
}

static audiodrv_t cs461x_audio_driver = {
  cs461x_audio_open,
  cs461x_audio_close,
  cs461x_audio_output_block,
  cs461x_audio_start_input,
  cs461x_audio_ioctl,
  cs461x_audio_prepare_for_input,
  cs461x_audio_prepare_for_output,
  cs461x_audio_reset,
  NULL,
  NULL,
  cs461x_audio_reset_input,
  cs461x_audio_reset_output,
  cs461x_audio_trigger,
  cs461x_audio_set_rate,
  cs461x_audio_set_format,
  cs461x_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  cs461x_alloc_buffer,
  cs461x_free_buffer,
  NULL,
  NULL,
  cs461x_get_buffer_pointer
};

/***********************MIDI PORT ROUTINES ****************/

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

  if (devc->midi_opened)
    {
      return OSS_EBUSY;
    }
  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  devc->midi_input_intr = inputbyte;
  devc->midi_opened = mode;

  /* first reset the MIDI port */
  PokeBA0 (devc, BA0_MIDCR, 0x10);
  PokeBA0 (devc, BA0_MIDCR, 0x00);

  /* Now check if we're in Read or Write mode */
  if (mode & OPEN_READ)
    {
      /* enable MIDI Input intr and receive enable */
      PokeBA0 (devc, BA0_MIDCR, MIDCR_RXE | MIDCR_RIE);
    }

  if (mode & OPEN_WRITE)
    {
      /* enable MIDI transmit enable without interrupt mode */
      PokeBA0 (devc, BA0_MIDCR, MIDCR_TXE);
    }
  PokeBA0 (devc, BA0_HICR, HICR_IEV | HICR_CHGM);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

/*ARGSUSED*/
static void
cs461x_midi_close (int dev, int mode)
{
  cs461x_devc *devc = (cs461x_devc *) midi_devs[dev]->devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
/* Reset the device*/
  PokeBA0 (devc, BA0_MIDCR, 0x10);
  PokeBA0 (devc, BA0_MIDCR, 0x00);
  devc->midi_opened = 0;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

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

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  uart_stat = PeekBA0 (devc, BA0_MIDSR);

/* Check if Transmit buffer full flag is set - if so return */
  if ((uart_stat & MIDSR_TBF))
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return 0;
    }
/* Now put the MIDI databyte in the write port */
  PokeBA0 (devc, BA0_MIDWP, midi_byte);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 1;
}

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

static midi_driver_t cs461x_midi_driver = {
  cs461x_midi_open,
  cs461x_midi_close,
  cs461x_midi_ioctl,
  cs461x_midi_out
};

static int
init_cs461x (cs461x_devc * devc)
{

  int my_mixer, my_mixer2, i;
  unsigned int ulCount = 0;
  unsigned int ulStatus = 0;
  unsigned int tmp;
  int first_dev = 0;
  int adev;
  oss_native_word phaddr;

/****************BEGIN HARDWARE INIT*****************/
  /*
   * First, blast the clock control register to zero so that the PLL starts
   * out in a known state, and blast the master serial port control register
   * to zero so that the serial ports also start out in a known state.
   */
  PokeBA0 (devc, BA0_CLKCR1, 0x0);
  PokeBA0 (devc, BA0_SERMC1, 0x0);
  oss_udelay (1000);
  /*
   * If we are in AC97 mode, then we must set the part to a host controlled
   * AC-link.  Otherwise, we won't be able to bring up the link.
   */
  if (devc->dual_codec)
    PokeBA0 (devc, BA0_SERACC,
	     SERACC_HSP | SERACC_TWO_CODECS | SERACC_CODEC_TYPE_2_0);
  else
    PokeBA0 (devc, BA0_SERACC, SERACC_HSP | SERACC_CODEC_TYPE_1_03);	/* AC97 1.03 */

  /*
   * Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97
   * spec) and then drive it high.  This is done for non AC97 modes since
   * there might be logic external to the CS461x that uses the ARST# line
   * for a reset.
   */
  PokeBA0 (devc, BA0_ACCTL, 1);
  oss_udelay (10000);
  PokeBA0 (devc, BA0_ACCTL, 0);
  oss_udelay (10000);
  PokeBA0 (devc, BA0_ACCTL, ACCTL_RSTN);
  /*
   * The first thing we do here is to enable sync generation.  As soon
   * as we start receiving bit clock, we'll start producing the SYNC
   * signal.
   */
  PokeBA0 (devc, BA0_ACCTL, ACCTL_ESYN | ACCTL_RSTN);

  /*
   * Now wait for a short while to allow the AC97 part to start
   * generating bit clock (so we don't try to start the PLL without an
   * input clock).
   */
  oss_udelay (500000);
  /*
   * Set the serial port timing configuration, so that
   * the clock control circuit gets its clock from the correct place.
   */
  PokeBA0 (devc, BA0_SERMC1, SERMC1_PTC_AC97);
  oss_udelay (500000);
  /*
   * Write the selected clock control setup to the hardware.  Do not turn on
   * SWCE yet (if requested), so that the devices clocked by the output of
   * PLL are not clocked until the PLL is stable.
   */
  PokeBA0 (devc, BA0_PLLCC, PLLCC_LPF_1050_2780_KHZ | PLLCC_CDR_73_104_MHZ);
  PokeBA0 (devc, BA0_PLLM, 0x3a);
  PokeBA0 (devc, BA0_CLKCR2, CLKCR2_PDIVS_8);

  /*
   * Power up the PLL.
   */
  PokeBA0 (devc, BA0_CLKCR1, CLKCR1_PLLP);

  /*
   * Wait until the PLL has stabilized.
   */
  oss_udelay (100000);
  /*
   * Turn on clocking of the core so that we can setup the serial ports.
   */
  tmp = PeekBA0 (devc, BA0_CLKCR1) | CLKCR1_SWCE;
  PokeBA0 (devc, BA0_CLKCR1, tmp);

  /* Clear the FIFOs */
  clear_serial_fifo (devc);
  /* PokeBA0 (devc, BA0_SERBSP, 0); */

  /*
   * Write the serial port configuration to the part.  The master
   * enable bit is not set until all other values have been written.
   */
  PokeBA0 (devc, BA0_SERC1, SERC1_SO1F_AC97 | SERC1_SO1EN);
  PokeBA0 (devc, BA0_SERC2, SERC2_SI1F_AC97 | SERC1_SO1EN);
  PokeBA0 (devc, BA0_SERMC1, SERMC1_PTC_AC97 | SERMC1_MSPE);

  PokeBA0 (devc, BA0_SERC7, SERC7_ASDI2EN);
  PokeBA0 (devc, BA0_SERC3, 0);
  PokeBA0 (devc, BA0_SERC4, 0);
  PokeBA0 (devc, BA0_SERC5, 0);
  PokeBA0 (devc, BA0_SERC6, 0);

  oss_udelay (100000);


  /* Wait for the codec ready signal from the AC97 codec. */
  for (ulCount = 0; ulCount < 1000; ulCount++)
    {
      /*
       * First, lets wait a short while to let things settle out a bit,
       * and to prevent retrying the read too quickly.
       */
      oss_udelay (10000);
      /*
       * Read the AC97 status register to see if we've seen a CODEC READY
       * signal from the AC97 codec.
       */
      ulStatus = PeekBA0 (devc, BA0_ACSTS);
      if (ulStatus & ACSTS_CRDY)
	break;
    }

  if (!(ulStatus & ACSTS_CRDY))
    {
      cmn_err (CE_WARN, "Initialize() Never read Codec Ready from AC97.\n");
      return 0;
    }

  if (devc->dual_codec)
    {
      for (ulCount = 0; ulCount < 1000; ulCount++)
	{
	  /*
	   *            
	   * First, lets wait a short while to let things settle out a bit,
	   * and to prevent retrying the read too quickly.
	   *
	   */
	  oss_udelay (10000);
	  /*
	   * Read the AC97 status register to see if we've seen a CODEC READY
	   * signal from the AC97 codec.
	   */
	  ulStatus = PeekBA0 (devc, BA0_ACSTS2);
	  if (ulStatus & ACSTS_CRDY)
	    break;
	}

      if (!(ulStatus & ACSTS_CRDY))
	{
	  cmn_err (CE_WARN,
		   "Initialize() Never read Codec Ready from second AC97.\n");
	}
    }
  /*
   * Assert the vaid frame signal so that we can start sending commands
   * to the AC97 codec.
   */
  PokeBA0 (devc, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN);
  /*
   * Wait until we've sampled input slots 3 and 4 as valid, meaning that
   * the codec is pumping ADC data across the AC-link.
   */
  for (ulCount = 0; ulCount < 1000; ulCount++)
    {
      /*
       * First, lets wait a short while to let things settle out a bit,
       * and to prevent retrying the read too quickly.
       */
      oss_udelay (10000);
      /*
       * Read the input slot valid register and see if input slots 3 and
       * 4 are valid yet.
       */
      ulStatus = PeekBA0 (devc, BA0_ACISV);
      if ((ulStatus & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4))
	break;
    }
  /*
   * Make sure we sampled valid input slots 3 and 4.  If not, then return
   * an error.
   */
  if ((ulStatus & (ACISV_ISV3 | ACISV_ISV4)) != (ACISV_ISV3 | ACISV_ISV4))
    cmn_err (CE_WARN, "Initialize(%x) Never sampled ISV3 & ISV4 from AC97.\n",
	     (int) ulStatus);
  /*
   * Now, assert valid frame and the slot 3 and 4 valid bits.  This will
   * commense the transfer of digital audio data to the AC97 codec.
   */
  PokeBA0 (devc, BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4);

  /* Reset the Processor */
  cs461x_reset_processor (devc);
  /* Now download the image */
  install_ucode (devc);

  devc->processor_started = 0;

  /* Save the play and capture parameters and write 0 to stop DMA */
  devc->PCTL = READ1L (BA1_PCTL) & 0xFFFF0000;
  WRITE1L (BA1_PCTL, devc->PCTL & 0x0000FFFF);
  devc->CCTL = READ1L (BA1_CCTL) & 0x0000FFFF;
  WRITE1L (BA1_CCTL, devc->CCTL & 0xFFFF0000);

  /*
   *  Enable interrupts on the part.
   */
  PokeBA0 (devc, BA0_HICR, HICR_IEV | HICR_CHGM);

  ulStatus = READ1L (BA1_PFIE);
  ulStatus &= ~0x0000f03f;
  WRITE1L (BA1_PFIE, ulStatus);	/* playback interrupt enable */

  ulStatus = READ1L (BA1_CIE);
  ulStatus &= ~0x0000003f;
  ulStatus |= 0x00000001;
  WRITE1L (BA1_CIE, ulStatus);	/* capture interrupt enable */

/****** END OF HARDWARE INIT *****/

  my_mixer =
    ac97_install (&devc->ac97devc, "CS461x AC97 Mixer", ac97_read, ac97_write,
		  devc, devc->osdev);

  if (my_mixer >= 0)
    {
      devc->mixer_dev = my_mixer;
      if (devc->subsysid == 0x5053)	/* SantaCruz has dual codec */
	{
	  my_mixer2 =
	    ac97_install (&devc->ac97devc2, "CS4630 AC97 Secondary",
			  ac97_read2, ac97_write2, devc, devc->osdev);
	  if (my_mixer2 >= 0)
	    {
	      devc->mixer2_dev = my_mixer2;
	      ac97_write2 (devc, BA0_AC97_6CH_VOL_C_LFE, 0x7FFF);
	      ac97_write2 (devc, BA0_AC97_6CH_VOL_SURROUND, 0x7FFF);
	    }
	}
    }
  else
    return 0;

  /* Turn on amp on the CS4297 on TB/Videologic */
  if (devc->subsysid == 0x5053)
    {
      unsigned short u16PinConfig, u16LogicType, u16ValidSlots, u16Idx;
      unsigned short u16Status = 0;
      int i;


      ac97_write (devc, 0x26, ac97_read (devc, 0x26) | 0x8000);
#if 0
      /* Set SPDIF */
      PokeBA0 (devc, BA0_ASER_MASTER, 1);	/*ASER_MASTER_ME */
      WRITE1L (0x8049, 0x00000001);
#endif
      /*
       *  Set GPIO pin's 7 and 8 so that they are configured for output.
       */
      u16PinConfig = ac97_read2 (devc, BA0_AC97_GPIO_PIN_CONFIG);
      u16PinConfig &= 0x27F;
      ac97_write2 (devc, BA0_AC97_GPIO_PIN_CONFIG, u16PinConfig);

      /*
       * Set GPIO pin's 7 and 8 so that they are compatible with CMOS logic.
       */
      u16LogicType = ac97_read2 (devc, BA0_AC97_GPIO_PIN_TYPE);
      u16LogicType &= 0x27F;
      ac97_write2 (devc, BA0_AC97_GPIO_PIN_TYPE, u16LogicType);

      u16ValidSlots = PeekBA0 (devc, BA0_ACOSV);
      u16ValidSlots |= 0x200;
      PokeBA0 (devc, BA0_ACOSV, u16ValidSlots);

      /*
       * Fill slots 12 with the correct value for the GPIO pins. 
       */
      for (u16Idx = 0x90; u16Idx <= 0x9F; u16Idx++)
	{

	  /*
	   * Initialize the fifo so that bits 7 and 8 are on.
	   *
	   * Remember that the GPIO pins in bonzo are shifted by 4 bits to
	   * the left.  0x1800 corresponds to bits 7 and 8.
	   */
	  PokeBA0 (devc, BA0_SERBWP, 0x1800);

	  /*
	   * Make sure the previous FIFO write operation has completed.
	   */
	  for (i = 0; i < 5; i++)
	    {
	      u16Status = PeekBA0 (devc, BA0_SERBST);
	      if (!(u16Status & SERBST_WBSY))
		{
		  break;
		}
	      oss_udelay (10000);
	    }

	  /*
	   * Write the serial port FIFO index.
	   */
	  PokeBA0 (devc, BA0_SERBAD, u16Idx);

	  /*
	   * Tell the serial port to load the new value into the FIFO location.
	   */
	  PokeBA0 (devc, BA0_SERBCM, SERBCM_WRC);
	}
    }
  /* Hercules Game Theater Amp */
  if (devc->subsysid == 0x1681)
    {
      PokeBA0 (devc, BA0_EGPIODR, EGPIODR_GPOE2);	/* enable EGPIO2 output */
      PokeBA0 (devc, BA0_EGPIOPTR, EGPIOPTR_GPPT2);	/* open-drain on output */

    }

#ifdef USE_SG
  devc->play_sgbuf =
    (unsigned int *) CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS,
				    &phaddr, devc->play_sgbuf_dma_handle);
  if (devc->play_sgbuf == NULL)
    {
      cmn_err (CE_WARN,
	       "cs461x: Failed to allocate play scatter/gather buffer.\n");
      return 0;
    }
  devc->play_sgbuf_phys = phaddr;
#endif

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

      if (i == 0)
	{
	  strcpy (tmp_name, devc->chip_name);
	  caps |= ADEV_DUPLEX;
	}
      else
	{
	  strcpy (tmp_name, devc->chip_name);
	  caps |= ADEV_DUPLEX | ADEV_SHADOW;
	}

      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&cs461x_audio_driver,
					sizeof (audiodrv_t),
					caps,
					AFMT_S16_LE | AFMT_U8, devc, -1)) < 0)
	{
	  adev = -1;
	  return 0;
	}
      else
	{
	  if (i == 0)
	    first_dev = adev;
	  audio_engines[adev]->portc = portc;
	  audio_engines[adev]->mixer_dev = my_mixer;
	  audio_engines[adev]->rate_source = first_dev;
	  audio_engines[adev]->min_rate = 5000;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->min_block = 4096;
	  audio_engines[adev]->max_block = 4096;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  portc->open_mode = 0;
	  portc->audiodev = adev;
	  portc->audio_enabled = 0;
	  portc->trigger_bits = 0;
#ifdef CONFIG_OSS_VMIX
	  if (i == 0)
	     vmix_attach_audiodev(devc->osdev, adev, -1, 0);
#endif
	}

    }

#ifndef sparc
  devc->midi_dev =
    oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "CS461x",
			 "CS461x MIDI Port", &cs461x_midi_driver,
			 sizeof (midi_driver_t),
			 0, devc, devc->osdev);
#endif
  devc->midi_opened = 0;
  return 1;
}

int
oss_cs461x_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision, pci_irq_inta;
  unsigned short pci_command, vendor, device;
  unsigned int ioaddr;
  int err;
  cs461x_devc *devc;

  DDB (cmn_err (CE_WARN, "Entered CS461x probe routine\n"));
#if 0
  if (cs461x_clkrun_fix)
    while ((osdev = (pci_find_class (0x680 << 8, osdev))))

      {
	unsigned char pp;
	unsigned int port, control;
	unsigned short vendor, device;

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

	if (vendor != 0x8086 || device != 0x7113)
	  continue;

	pci_read_config_byte (osdev, 0x41, &pp);
	port = pp << 8;
	control = INW (devc->osdev, port + 0x10);
	OUTW (devc->osdev, control | 0x2000, port + 0x10);
	oss_udelay (100);
	OUTW (devc->osdev, control & ~0x2000, port + 0x10);
      }
#endif

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

  if (vendor != CRYSTAL_VENDOR_ID || device != CRYSTAL_CS461x_ID)
    return 0;

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

  devc->osdev = osdev;
  osdev->devc = devc;
  devc->open_mode = 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_byte (osdev, PCI_INTERRUPT_LINE + 1, &pci_irq_inta);
  pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &ioaddr);
  devc->bar0addr = ioaddr;
  pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_1, &ioaddr);
  devc->bar1addr = ioaddr;
  pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &devc->subsysid);

  switch (device)
    {
    case CRYSTAL_CS461x_ID:
      devc->chip_name = "Crystal CS461x";
      break;

    case CRYSTAL_CS4610_ID:
      devc->chip_name = "Crystal CS4610";
      break;

    case CRYSTAL_CS4615_ID:
      devc->chip_name = "Crystal CS4615";
      break;

    default:
      devc->chip_name = "CrystalPCI";
    }

  if (devc->subsysid == 0x5053)
    {
      devc->chip_name = "Crystal CS4630";
      devc->dual_codec = 1;
    }

  /* activate the device enable bus master/memory space */
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  if ((devc->bar0addr == 0) || (devc->bar1addr == 0))
    {
      cmn_err (CE_WARN, "Undefined MEMORY I/O address.\n");
      return 0;
    }

  if (pci_irq_line == 0)
    {
      cmn_err (CE_WARN, "IRQ not assigned by BIOS.\n");
      return 0;
    }

  /* Map the shared memory area */
  devc->bar0virt =
    (unsigned int *) MAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, 0x2000);
  devc->bar1virt =
    (unsigned int *) MAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, 0x40000);
  devc->dwRegister0 = (unsigned int *) devc->bar0virt;
  devc->wRegister0 = (unsigned short *) devc->bar0virt;
  devc->bRegister0 = (unsigned char *) devc->bar0virt;
  devc->dwRegister1 = (unsigned int *) devc->bar1virt;
  devc->wRegister1 = (unsigned short *) devc->bar1virt;
  devc->bRegister1 = (unsigned char *) devc->bar1virt;

  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, cs461xintr, NULL)) < 0)
    {
      cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err);
      return 0;
    }

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


int
oss_cs461x_detach (oss_device_t * osdev)
{
  cs461x_devc *devc = (cs461x_devc *) osdev->devc;


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

  PokeBA0 (devc, BA0_HICR, 0x02);	/*enable intena */
  cs461x_reset_processor (devc);
#ifdef USE_SG
  CONTIG_FREE (devc->osdev, devc->play_sgbuf, 4096, devc->play_sgbuf_dma_handle);
#endif

  oss_unregister_interrupts (devc->osdev);

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

  UNMAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, devc->bar0virt, 0x2000);
  UNMAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, devc->bar1virt, 0x40000);

  devc->bar0addr = 0;
  devc->bar1addr = 0;

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