/*
 * Purpose: Driver for Crystal 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 "oss_cs4281_cfg.h"
#include "midi_core.h"
#include "ac97.h"
#include "oss_pci.h"
#include "cs4281.h"

#define CRYSTAL_VENDOR_ID	0x1013
#define CRYSTAL_CS4281_ID	0x6005

#if 1
#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 READL(a) (devc->dwRegister0[a>>2])
#define WRITEL(a, d) devc->dwRegister0[a>>2] = d
#else
#define WRITEB(a,d) PCI_WRITEB(devc->osdev, d, devc->bRegister0[a])
#define READB(a) PCI_READB(devc->osdev, devc->bRegister0[a])
#define WRITEW(a,d) PCI_WRITEW(devc->osdev, d, devc->wRegister0[a>>1])
#define READW(a) PCI_READW(devc->osdev, devc->wRegister0[a>>1])
#define READL(a) PCI_READL(devc->osdev, devc->dwRegister0[a>>2])
#define WRITEL(a, d) PCI_WRITEL(devc->osdev, d, devc->dwRegister0[a>>2])
#endif

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

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

#define MAX_PORTC 2

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

typedef struct cs4281_devc
{
  oss_device_t *osdev;
  char *chip_name;
  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;
  volatile unsigned char intr_mask;
  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;
  int mixer_dev;

  /* Audio parameters */
  cs4281_portc portc[MAX_PORTC];
  int open_mode;
  int fm_attached, mpu_attached;
}
cs4281_devc;



static int
ac97_read (void *devc_, int reg)
{
  cs4281_devc *devc = devc_;
  int count;
  oss_native_word 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 = READL (BA0_ACSDA);
  /* Get the actual AC97 register from the offset */
  WRITEL (BA0_ACCAD, reg);
  WRITEL (BA0_ACCDA, 0);
  WRITEL (BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN);

  /* Wait for the read to occur. */
  for (count = 0; count < 500; count++)
    {
      /* First, we want to wait for a short time. */
      oss_udelay (10);
      /*
       * Now, check to see if the read has completed.
       * ACCTL = 460h, DCV should be reset by now and 460h = 17h
       */
      status = READL (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 < 500; count++)
    {
      /*
       * Read the AC97 status register.
       * ACSTS = Status Register = 464h
       */
      status = READL (BA0_ACSTS);
      /*
       * See if we have valid status.
       * VSTS - Valid Status
       */
      if (status & ACSTS_VSTS)
	break;

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

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

static int
ac97_write (void *devc_, int reg, int data)
{
  cs4281_devc *devc = devc_;
  int count;
  oss_native_word status, flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  WRITEL (BA0_ACCAD, reg);
  WRITEL (BA0_ACCDA, data);
  WRITEL (BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | ACCTL_ESYN);
  for (count = 0; count < 500; count++)
    {
      /* First, we want to wait for a short time. */
      oss_udelay (10);
      /* Now, check to see if the write has completed. */
      /* ACCTL = 460h, DCV should be reset by now and 460h = 07h */
      status = READL (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
cs4281intr (oss_device_t * osdev)
{
  cs4281_devc *devc = (cs4281_devc *) osdev->devc;
  cs4281_portc *portc;
  oss_native_word status, uart_stat;
  int i;
  int serviced = 0;

/* Read the Interrupt Status Register */
  status = READL (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))
    {
      serviced = 1;
      uart_stat = READL (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 = READL (BA0_MIDRP);

	  if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
	    devc->midi_input_intr (devc->midi_dev, d);
	  uart_stat = READL (BA0_MIDSR);
	}
    }
/* Audio interrupt handling */
  if (status & (HISR_INTENA | HISR_DMAI))
    for (i = 0; i < MAX_PORTC; i++)
      {
	portc = &devc->portc[i];
	if ((status & HISR_DMA0) && (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	  {
	    dmap_t *dmapout = audio_engines[portc->audiodev]->dmap_out;
	    unsigned int currdac;
	    int n;

	    serviced = 1;
	    READL (BA0_HDSR0);	/* ack the DMA interrupt */
	    currdac = READL (BA0_DCA0) - dmapout->dmabuf_phys;
	    currdac /= dmapout->fragment_size;
	    n = 0;
	    while (dmap_get_qhead (dmapout) != currdac
		   && n++ < dmapout->nfrags)
	      oss_audio_outputintr (portc->audiodev, 1);
	  }

	if ((status & HISR_DMA1) && (portc->trigger_bits & PCM_ENABLE_INPUT))
	  {
	    dmap_t *dmapin = audio_engines[portc->audiodev]->dmap_in;
	    unsigned int curradc;
	    int n;

	    serviced = 1;
	    READL (BA0_HDSR1);	/* ack the DMA interrupt */
	    curradc = READL (BA0_DCA1) - dmapin->dmabuf_phys;
	    curradc /= dmapin->fragment_size;
	    n = 0;
	    while (dmap_get_qtail (dmapin) != curradc && n++ < dmapin->nfrags)
	      oss_audio_inputintr (portc->audiodev, 0);
	  }
	WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM);
      }
  return serviced;
}

static int
cs4281_audio_set_rate (int dev, int arg)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  if (arg == 0)
    return portc->speed;
  if (arg > 48000)
    arg = 48000;
  if (arg < 6023)
    arg = 6023;
  portc->speed = arg;
  return portc->speed;
}

static short
cs4281_audio_set_channels (int dev, short arg)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  if ((arg != 1) && (arg != 2))
    return portc->channels;
  portc->channels = arg;
  return portc->channels;
}

static unsigned int
cs4281_audio_set_format (int dev, unsigned int arg)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  if (arg == 0)
    return portc->bits;
  if (!(arg & (AFMT_U8 | AFMT_S16_LE)))
    return portc->bits;
  portc->bits = arg;
  return portc->bits;
}

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

static void cs4281_audio_trigger (int dev, int state);
static void
cs4281_audio_reset (int dev)
{
  cs4281_audio_trigger (dev, 0);
}

static void
cs4281_audio_reset_input (int dev)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  cs4281_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
cs4281_audio_reset_output (int dev)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  cs4281_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
cs4281_audio_open (int dev, int mode, int open_flags)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  cs4281_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;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static void
cs4281_audio_close (int dev, int mode)
{
  cs4281_portc *portc = audio_engines[dev]->portc;
  cs4281_devc *devc = audio_engines[dev]->devc;
  cs4281_audio_reset (dev);
  portc->open_mode = 0;
  devc->open_mode &= ~mode;
  portc->audio_enabled &= ~mode;
}

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

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

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

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

static void
cs4281_audio_trigger (int dev, int state)
{
  cs4281_devc *devc = audio_engines[dev]->devc;
  cs4281_portc *portc = audio_engines[dev]->portc;
  oss_native_word tmp1;
  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))
	    {
	      /* Clear DMA0 channel Mask bit. Start Playing. */
	      tmp1 = READL (BA0_DCR0) & ~DCRn_MSK;	/*enable DMA */
	      WRITEL (BA0_DCR0, tmp1);	/* (154h) */
	      WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM);	/*enable intr */
	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
	      tmp1 = READL (BA0_DCR0) & ~DCRn_MSK;
	      WRITEL (BA0_DCR0, tmp1 | DCRn_MSK);
	    }
	}
    }
  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      /* Clear DMA1 channel Mask bit. Start recording. */
	      tmp1 = READL (BA0_DCR1) & ~DCRn_MSK;
	      WRITEL (BA0_DCR1, tmp1);	/* (15ch) */
	      WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM);	/*Set INTENA=1. */
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_INPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_INPUT;
	      tmp1 = READL (BA0_DCR1) & ~DCRn_MSK;
	      WRITEL (BA0_DCR1, tmp1 | DCRn_MSK);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static int
cs4281_rate (oss_native_word rate)
{
  int val = 0;

  switch (rate)
    {
    case 8000:
      val = 5;
      break;
    case 11025:
      val = 4;
      break;
    case 16000:
      val = 3;
      break;
    case 22050:
      val = 2;
      break;
    case 44100:
      val = 1;
      break;
    case 48000:
      val = 0;
      break;
    default:
      val = 1536000 / rate;
      break;
    }
  return val;
}

/*ARGSUSED*/
static int
cs4281_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  cs4281_devc *devc = audio_engines[dev]->devc;
  cs4281_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  int count = dmap->bytes_in_use;
  oss_native_word recordFormat;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  /* set the record rate */
  WRITEL (BA0_ADCSR, cs4281_rate (portc->speed));	/* (748h) */
  /* Start with defaults for the record format */
  /* reg & modify them for the current case. */
  recordFormat = DMRn_DMA | DMRn_AUTO | DMRn_TR_WRITE;
  if (portc->channels == 1)	/* If mono, */
    recordFormat |= DMRn_MONO;	/* Turn on mono bit. */
  if (portc->bits == 8)		/* If 8-bit, */
    recordFormat |= (DMRn_SIZE8 | DMRn_USIGN);	/* turn on 8bit/unsigned. */
  WRITEL (BA0_DMR1, recordFormat);
  /* set input gain to 0db */
  /* ac97_write(devc, BA0_AC97_RECORD_GAIN, 0x0808);  */
  if (portc->channels == 2)	/* If stereo, */
    count /= 2;			/* halve DMA count(stereo); */
  if (portc->bits == 16)	/* If 16-bit, */
    count /= 2;
  /* Set the physical play buffer address DMA1 Base & Current. */
  WRITEL (BA0_DBA1, dmap->dmabuf_phys);	/* (118h) */
  /* Set the sample count(-1) in DMA Base Count register 1. */
  WRITEL (BA0_DBC1, count - 1);
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

/*ARGSUSED*/
static int
cs4281_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  cs4281_devc *devc = audio_engines[dev]->devc;
  cs4281_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  int count = dmap->bytes_in_use;
  oss_native_word playFormat;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  /* Set the sample rate converter */
  WRITEL (BA0_DACSR, cs4281_rate (portc->speed));
  playFormat = DMRn_DMA | DMRn_AUTO | DMRn_TR_READ | (1 << 6);
  if (portc->channels == 1)	/* If stereo, */
    playFormat |= DMRn_MONO;	/* Turn on mono bit. */
  if (portc->bits == 8)		/* If 16-bit, */
    playFormat |= (DMRn_SIZE8 | DMRn_USIGN);	/* turn on 8-bit/unsigned. */
  WRITEL (BA0_DMR0, playFormat);	/* (150h) */
  if (portc->channels == 2)	/* If stereo, */
    count /= 2;			/* halve DMA count(stereo); */
  if (portc->bits == 16)	/* If 16-bit, */
    count /= 2;
  /* Set the physical play buffer address DMA0 Base & Current. */
  WRITEL (BA0_DBA0, dmap->dmabuf_phys & ~0x3);	/* (118h) */
  /* Set the sample count(-1) in DMA Base Count register 0. */
  WRITEL (BA0_DBC0, count - 1);
  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static int
cs4281_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  cs4281_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 = READL (BA0_DCA0);
    }
  if (direction == PCM_ENABLE_INPUT)
    {
      ptr = READL (BA0_DCA1);
    }
  ptr -= dmap->dmabuf_phys;
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr;
}

static audiodrv_t cs4281_audio_driver = {
  cs4281_audio_open,
  cs4281_audio_close,
  cs4281_audio_output_block,
  cs4281_audio_start_input,
  cs4281_audio_ioctl,
  cs4281_audio_prepare_for_input,
  cs4281_audio_prepare_for_output,
  cs4281_audio_reset,
  NULL,
  NULL,
  cs4281_audio_reset_input,
  cs4281_audio_reset_output,
  cs4281_audio_trigger,
  cs4281_audio_set_rate,
  cs4281_audio_set_format,
  cs4281_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* cs4281_alloc_buffer, */
  NULL,				/* cs4281_free_buffer, */
  NULL,
  NULL,
  cs4281_get_buffer_pointer
};

/***********************MIDI PORT ROUTINES ****************/
/*ARGSUSED*/
static int
cs4281_midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
		  oss_midi_inputbuf_t inputbuf,
		  oss_midi_outputintr_t outputintr)
{
  cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc;
  if (devc->midi_opened)
    {
      return OSS_EBUSY;
    }

  devc->midi_input_intr = inputbyte;
  devc->midi_opened = mode;
  /* first reset the MIDI port */
  WRITEL (BA0_MIDCR, 0x10);
  WRITEL (BA0_MIDCR, 0x00);
  /* Now check if we're in Read or Write mode */
  if (mode & OPEN_READ)
    {
      /* enable MIDI Input intr and receive enable */
      WRITEL (BA0_MIDCR, MIDCR_RXE | MIDCR_RIE);
    }

  if (mode & OPEN_WRITE)
    {
      /* enable MIDI transmit enable */
      WRITEL (BA0_MIDCR, MIDCR_TXE);
    }
  return 0;
}

/*ARGSUSED*/
static void
cs4281_midi_close (int dev, int mode)
{
  cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc;
/* Reset the device*/
  WRITEL (BA0_MIDCR, 0x10);
  WRITEL (BA0_MIDCR, 0x00);
  devc->midi_opened = 0;
}

static int
cs4281_midi_out (int dev, unsigned char midi_byte)
{
  cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc;
  unsigned char uart_stat = READL (BA0_MIDSR);
/* Check if Transmit buffer full flag is set - if so return */
  if ((uart_stat & MIDSR_TBF))
    return 0;
/* Now put the MIDI databyte in the write port */
  WRITEL (BA0_MIDWP, midi_byte);
  return 1;
}

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

static midi_driver_t cs4281_midi_driver = {
  cs4281_midi_open,
  cs4281_midi_close,
  cs4281_midi_ioctl,
  cs4281_midi_out
};

static int
init_cs4281 (cs4281_devc * devc)
{

  int my_mixer, i;
  oss_native_word dwAC97SlotID, tmp1;
  int first_dev = 0;
/****************BEGIN HARDWARE INIT*****************/
  /* Setup CFLR */
  tmp1 = READL (BA0_CFLR);
  if (tmp1 != 0x01)
    {
      WRITEL (BA0_CFLR, 0x01);	/*set up AC97 mode */
      tmp1 = READL (BA0_CFLR);
      if (tmp1 != 0x01)
	{
	  tmp1 = READL (BA0_CWPR);
	  if (tmp1 != 0x4281)
	    WRITEL (BA0_CWPR, 0x4281);
	  tmp1 = READL (BA0_CWPR);
	  if (tmp1 != 0x4281)
	    {
	      cmn_err (CE_WARN, "Resetting AC97 failed\n");
	      return OSS_EIO;
	    }
	  WRITEL (BA0_CFLR, 0x1);
	  tmp1 = READL (BA0_CFLR);
	  if (tmp1 != 0x1)
	    {
	      cmn_err (CE_WARN, "Resetting AC97 still fails\n");
	      return OSS_EIO;
	    }
	}
    }
  /* Setup the FM and Joystick trap address for Legacy emulation */
  WRITEL (BA0_IOTCR, 0x1);
  WRITEL (BA0_IOTFM, 0xc0030388);
  WRITEL (BA0_IOTGP, 0xc0070200);
  /**************************************** */
  /*  Set up the Sound System Configuration */
  /**************************************** */
  /* Set the 'Configuration Write Protect' register */
  /* to 4281h.  Allows vendor-defined configuration */
  /* space between 0e4h and 0ffh to be written. */
  WRITEL (BA0_CWPR, 0x4281);	/* (3e0h) */
  if ((tmp1 = READL (BA0_SERC1)) != (SERC1_SO1EN | SERC1_SO1F_AC97))
    {
      cmn_err (CE_WARN, "SERC1: AC97 check failed\n");
      return OSS_EIO;
    }
  /* setup power management to full power */
  WRITEL (BA0_SSPM, 0x7E);
  /* 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. */
  WRITEL (BA0_CLKCR1, 0);	/* (400h) */
  WRITEL (BA0_SERMC, 0);	/* (420h) */
  /* (1) 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. */
  WRITEL (BA0_ACCTL, 0);
  oss_udelay (50);
  WRITEL (BA0_SPMC, 0);		/* (3ech) */
  oss_udelay (50);
  WRITEL (BA0_SPMC, SPMC_RSTN);
  oss_udelay (50000);
  WRITEL (BA0_SERMC, SERMC_PTC_AC97 | SERMC_MSPE | 0x10000);
  /* (3) Turn on the Sound System Clocks. */
  WRITEL (BA0_CLKCR1, CLKCR1_DLLP);	/* (400h) */
  /* Wait for the PLL to stabilize. */
  oss_udelay (50000);
  /* Turn on clocking of the core (CLKCR1(400h) = 0x00000030) */
  WRITEL (BA0_CLKCR1, CLKCR1_DLLP | CLKCR1_SWCE);
  /* (5) Wait for clock stabilization. */
  for (tmp1 = 0; tmp1 < 100; tmp1++)
    {
      if (READL (BA0_CLKCR1) & CLKCR1_DLLRDY)
	break;
      oss_udelay (50000);
    }
  if (!(READL (BA0_CLKCR1) & CLKCR1_DLLRDY))
    {
      cmn_err (CE_WARN, "DLLRDY Clock not ready\n");
      return OSS_EIO;
    }
  /* (6) Enable ASYNC generation. */
  WRITEL (BA0_ACCTL, ACCTL_ESYN);	/* (460h) */
  /* 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.) */
  /* (7) Wait for the codec ready signal from the AC97 codec. */
  for (tmp1 = 0; tmp1 < 100; tmp1++)
    {
      /* Delay a mil to let things settle out and */
      /* to prevent retrying the read too quickly. */
      if (READL (BA0_ACSTS) & ACSTS_CRDY)	/* If ready,  (464h) */
	break;			/*   exit the 'for' loop. */
      oss_udelay (50000);
    }
  if (!(READL (BA0_ACSTS) & ACSTS_CRDY))	/* If never came ready, */
    {
      cmn_err (CE_WARN, "AC97 not ready\n");
      return OSS_EIO;		/*   exit initialization. */
    }
  /* (8) Assert the 'valid frame' signal so we can */
  /* begin sending commands to the AC97 codec. */
  WRITEL (BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN);	/* (460h) */

  /* (11) Wait until we've sampled input slots 3 & 4 as valid, meaning */
  /* that the codec is pumping ADC data across the AC link. */
  for (tmp1 = 0; tmp1 < 100; tmp1++)
    {
      /* Read the input slot valid register;  See */
      /* if input slots 3 and 4 are valid yet. */
      if ((READL (BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) ==	/* (474h) */
	  (ACISV_ISV3 | ACISV_ISV4))
	break;			/* Exit the 'for' if slots are valid. */
      oss_udelay (50000);
    }
  /* If we never got valid data, exit initialization. */
  if ((READL (BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) !=
      (ACISV_ISV3 | ACISV_ISV4))
    {
      cmn_err (CE_WARN, "AC97 Slot not valid\n");
      return OSS_EIO;		/* If no valid data, exit initialization. */
    }
  /* (12) Start digital data transfer of audio data to the codec. */
  WRITEL (BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4);	/* (468h) */

  /* For playback, we map AC97 slot 3 and 4(Left */
  /* & Right PCM playback) to DMA Channel 0. */
  /* Set the fifo to be 31 bytes at offset zero. */
  dwAC97SlotID = 0x01001F00;	/* FCR0.RS[4:0]=1(=>slot4, right PCM playback). */
  /* FCR0.LS[4:0]=0(=>slot3, left PCM playback). */
  /* FCR0.SZ[6-0]=15; FCR0.OF[6-0]=0. */
  WRITEL (BA0_FCR0, dwAC97SlotID);	/* (180h) */
  WRITEL (BA0_FCR0, dwAC97SlotID | FCRn_FEN);	/* Turn on FIFO Enable. */
  /* For capture, we map AC97 slot 10 and 11(Left */
  /* and Right PCM Record) to DMA Channel 1. */
  /* Set the fifo to be 31 bytes at offset 32. */
  dwAC97SlotID = 0x0B0A1F20;	/* FCR1.RS[4:0]=11(=>slot11, right PCM record). */
  /* FCR1.LS[4:0]=10(=>slot10, left PCM record). */
  /* FCR1.SZ[6-0]=15; FCR1.OF[6-0]=16. */
  WRITEL (BA0_FCR1, dwAC97SlotID);	/* (184h) */
  WRITEL (BA0_FCR1, dwAC97SlotID | FCRn_FEN);	/* Turn on FIFO Enable. */
  /* Map the Playback SRC to the same AC97 slots(3 & 4-- */
  /* --Playback left & right)as DMA channel 0. */
  /* Map the record SRC to the same AC97 slots(10 & 11-- */
  /* -- Record left & right) as DMA channel 1. */
  dwAC97SlotID = 0x0b0a0100;	/*SCRSA.PRSS[4:0]=1(=>slot4, right PCM playback). */
  /*SCRSA.PLSS[4:0]=0(=>slot3, left PCM playback). */
  /*SCRSA.CRSS[4:0]=11(=>slot11, right PCM record) */
  /*SCRSA.CLSS[4:0]=10(=>slot10, left PCM record). */
  WRITEL (BA0_SRCSA, dwAC97SlotID);	/* (75ch) */
  /* Set 'Half Terminal Count Interrupt Enable' and 'Terminal */
  /* Count Interrupt Enable' in DMA Control Registers 0 & 1. */
  /* Set 'MSK' flag to 1 to keep the DMA engines paused. */
  tmp1 = (DCRn_HTCIE | DCRn_TCIE | DCRn_MSK);	/* (00030001h) */
  WRITEL (BA0_DCR0, tmp1);	/* (154h) */
  WRITEL (BA0_DCR1, tmp1);	/* (15ch) */
  /* Set 'Auto-Initialize Control' to 'enabled'; For playback, */
  /* set 'Transfer Type Control'(TR[1:0]) to 'read transfer', */
  /* for record, set Transfer Type Control to 'write transfer'. */
  /* All other bits set to zero;  Some will be changed @ transfer start. */
  tmp1 = (DMRn_DMA | DMRn_AUTO | DMRn_TR_READ);	/* (20000018h) */
  WRITEL (BA0_DMR0, tmp1);	/* (150h) */
  tmp1 = (DMRn_DMA | DMRn_AUTO | DMRn_TR_WRITE);	/* (20000014h) */
  WRITEL (BA0_DMR1, tmp1);	/* (158h) */
  /* Enable DMA interrupts generally, and */
  /* DMA0 & DMA1 interrupts specifically. */
  tmp1 = READL (BA0_HIMR) & 0x7fbbfcff;
  WRITEL (BA0_HIMR, tmp1);
  /* set up some volume defaults */
  WRITEL (BA0_PPLVC, 0x0808);
  WRITEL (BA0_PPRVC, 0x0808);
  WRITEL (BA0_FMLVC, 0x0);
  WRITEL (BA0_FMRVC, 0x0);
/****** END OF HARDWARE INIT *****/


  my_mixer =
    ac97_install (&devc->ac97devc, "CS4281 AC97 Mixer", ac97_read, ac97_write,
		  devc, devc->osdev);
  if (my_mixer < 0)
    {
      return 0;
    }

  devc->mixer_dev = my_mixer;

  /* ac97_write(devc,  0x26, ac97_read(devc, 0x26) | 0x8000); */
  for (i = 0; i < MAX_PORTC; i++)
    {
      int adev;
      int caps = 0;
      cs4281_portc *portc = &devc->portc[i];
      char tmp_name[100];
      strcpy (tmp_name, devc->chip_name);

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

      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&cs4281_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]->rate_source = first_dev;
	  audio_engines[adev]->mixer_dev = my_mixer;
	  audio_engines[adev]->min_rate = 6023;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  portc->open_mode = 0;
	  portc->audiodev = adev;
	  portc->audio_enabled = 0;
#ifdef CONFIG_OSS_VMIX
	  if (i == 0)
	     vmix_attach_audiodev(devc->osdev, adev, -1, 0);
#endif
	}
    }

  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "CS4281", "CS4281 MIDI Port", &cs4281_midi_driver, sizeof (midi_driver_t),
					0, devc, devc->osdev);
  devc->midi_opened = 0;
  return 1;
}

int
oss_cs4281_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;
  cs4281_devc *devc;

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

  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_CS4281_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;

  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;

  /* 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, 4 * 1024);
  devc->bar1virt =
    (unsigned int *) MAP_PCI_MEM (devc->osdev, 1, devc->bar1addr,
				  1024 * 1024);
  devc->dwRegister0 = devc->bar0virt;
  devc->wRegister0 = (unsigned short *) devc->bar0virt;
  devc->bRegister0 = (unsigned char *) devc->bar0virt;
  devc->dwRegister1 = devc->bar1virt;
  devc->wRegister1 = (unsigned short *) devc->bar1virt;
  devc->bRegister1 = (unsigned char *) devc->bar1virt;

  devc->chip_name = "CS4281";
  devc->irq = pci_irq_line;
  devc->open_mode = 0;

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

  oss_register_device (osdev, devc->chip_name);

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


int
oss_cs4281_detach (oss_device_t * osdev)
{
  cs4281_devc *devc = (cs4281_devc *) osdev->devc;

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

  WRITEL (BA0_HICR, 0x02);	/*enable intena */

  oss_unregister_interrupts (devc->osdev);

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

  UNMAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, devc->bar0virt, 4 * 1024);
  UNMAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, devc->bar1virt, 1024 * 1024);

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

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