/*
 * Purpose: Creative/Ensoniq AudioPCI97  driver (ES1371/ES1373)
 *
 * This driver is used with the original Ensoniq AudioPCI97 card and many
 * PCI based Sound Blaster cards by Creative Technologies. For example
 * Sound Blaster PCI128 and Creative/Ectiva EV1938.
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

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

#include "apci97_cfg.h"
#include "midi_core.h"
#include "apci97.h"
#include "ac97.h"
#include "oss_pci.h"


extern int apci_latency;
extern int apci_spdif;

#define ENSONIQ_VENDOR_ID	0x1274
#define ECTIVA_VENDOR_ID	0x1102
#define ENSONIQ_ES1371		0x1371
#define ENSONIQ_ES5880      	0x8001
#define ENSONIQ_ES5880A     	0x8002
#define ENSONIQ_ES5880B     	0x5880
#define ECTIVA_ES1938		0x8938

#define MAX_PORTC 2

typedef struct apci97_portc
{

  /* Audio parameters */
  int audiodev;
  int open_mode;
  int trigger_bits;
  int audio_enabled;
  int speed, bits, channels;
  int atype;			/* 0=DAC/ADC, 1=Synth */
}
apci97_portc;

typedef struct apci97_devc
{
  oss_device_t *osdev;
  oss_mutex_t mutex, low_mutex;
  oss_native_word base;
  int irq;
  char *chip_name;
  int revision;

  apci97_portc portc[MAX_PORTC];
/*
 * Mixer
 */
  ac97_devc ac97devc;

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


void SRCRegWrite (apci97_devc * devc, unsigned short reg, unsigned short val);
void SRCSetRate (apci97_devc * devc, unsigned char base, unsigned short rate);


static int
ac97_read (void *devc_, int wAddr)
{
  apci97_devc *devc = devc_;
  int i, dtemp, dinit;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  dtemp = INL (devc->osdev, devc->base + CONC_dCODECCTL_OFF);
  /* wait for WIP to go away saving the current state for later */
  for (i = 0; i < 0x100UL; ++i)
    if (!(INL (devc->osdev, devc->base + CONC_dCODECCTL_OFF) & (1UL << 30)))
      break;

  /* write addr w/data=0 and assert read request ... */

  /* save the current state for later */
  dinit = INL (devc->osdev, devc->base + CONC_dSRCIO_OFF);

  /* enable SRC state data in SRC mux */
  for (i = 0; i < 0x100UL; ++i)
    if (!
	((dtemp =
	  INL (devc->osdev, devc->base + CONC_dSRCIO_OFF)) & SRC_BUSY))
      break;
  OUTL (devc->osdev, (dtemp & SRC_CTLMASK) | 0x00010000UL,
	devc->base + CONC_dSRCIO_OFF);

  /* wait for a SAFE time to write a read request and then do it, dammit */

  for (i = 0; i < 0x100UL; ++i)
    {
      if ((INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & 0x00070000UL) ==
	  0x00010000UL)
	break;
    }

  OUTL (devc->osdev,
	((int) wAddr << 16) | (1UL << 23), devc->base + CONC_dCODECCTL_OFF);

  /* restore SRC reg */
  for (i = 0; i < 0x100UL; ++i)
    if (!
	((dtemp =
	  INL (devc->osdev, devc->base + CONC_dSRCIO_OFF)) & SRC_BUSY))
      break;
  OUTL (devc->osdev, dinit, devc->base + CONC_dSRCIO_OFF);

  /* now wait for the stinkin' data (RDY) */
  for (i = 0; i < 0x100UL; ++i)
    if (INL (devc->osdev, devc->base + CONC_dCODECCTL_OFF) & (1UL << 31))
      break;
  dtemp = INL (devc->osdev, devc->base + CONC_dCODECCTL_OFF);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return dtemp & 0xffff;
}

static int
ac97_write (void *devc_, int wAddr, int wData)
{
  apci97_devc *devc = devc_;
  int i, dtemp, dinit;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  /* wait for WIP to go away */
  for (i = 0; i < 0x100UL; ++i)
    if (!(INL (devc->osdev, devc->base + CONC_dCODECCTL_OFF) & (1UL << 30)))
      break;

  /* save the current state for later */
  dinit = INL (devc->osdev, devc->base + CONC_dSRCIO_OFF);

  dtemp = INL (devc->osdev, devc->base + CONC_dSRCIO_OFF);
  /* enable SRC state data in SRC mux */
  for (i = 0; i < 0x100UL; ++i)
    if (!
	((dtemp =
	  INL (devc->osdev, devc->base + CONC_dSRCIO_OFF)) & SRC_BUSY))
      break;
  OUTL (devc->osdev, (dtemp & SRC_CTLMASK) | 0x00010000UL,
	devc->base + CONC_dSRCIO_OFF);

  /* wait for a SAFE time to write addr/data and then do it, dammit */
  for (i = 0; i < 0x100UL; ++i)
    {
      if ((INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & 0x00070000UL) ==
	  0x00010000UL)
	break;
    }

  OUTL (devc->osdev, ((int) wAddr << 16) | wData,
	devc->base + CONC_dCODECCTL_OFF);

  /* restore SRC reg */
  for (i = 0; i < 0x100UL; ++i)
    if (!(INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & SRC_BUSY))
      break;
  OUTL (devc->osdev, dinit, devc->base + CONC_dSRCIO_OFF);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return 0;
}


void
SRCInit (apci97_devc * devc)
{
  int i;

  /* Clear all SRC RAM then init - keep SRC disabled until done */
  for (i = 0; i < SRC_IOPOLL_COUNT; ++i)
    if (!(INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & SRC_BUSY))
      break;
  OUTL (devc->osdev, SRC_DISABLE, devc->base + CONC_dSRCIO_OFF);

  for (i = 0; i < 0x80; ++i)
    SRCRegWrite (devc, (unsigned short) i, 0U);

  SRCRegWrite (devc, SRC_SYNTH_BASE + SRC_TRUNC_N_OFF, 16 << 4);
  SRCRegWrite (devc, SRC_SYNTH_BASE + SRC_INT_REGS_OFF, 16 << 10);
  SRCRegWrite (devc, SRC_DAC_BASE + SRC_TRUNC_N_OFF, 16 << 4);
  SRCRegWrite (devc, SRC_DAC_BASE + SRC_INT_REGS_OFF, 16 << 10);
  SRCRegWrite (devc, SRC_SYNTH_VOL_L, 1 << 12);
  SRCRegWrite (devc, SRC_SYNTH_VOL_R, 1 << 12);
  SRCRegWrite (devc, SRC_DAC_VOL_L, 1 << 12);
  SRCRegWrite (devc, SRC_DAC_VOL_R, 1 << 12);
  SRCRegWrite (devc, SRC_ADC_VOL_L, 1 << 12);
  SRCRegWrite (devc, SRC_ADC_VOL_R, 1 << 12);

  /* default some rates */
  SRCSetRate (devc, SRC_SYNTH_BASE, 8000);
  SRCSetRate (devc, SRC_DAC_BASE, 8000);
  SRCSetRate (devc, SRC_ADC_BASE, 8000);

  /* now enable the whole deal */
  for (i = 0; i < SRC_IOPOLL_COUNT; ++i)
    if (!(INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & SRC_BUSY))
      break;
  OUTL (devc->osdev, 0, devc->base + CONC_dSRCIO_OFF);

  return;
}

unsigned short
SRCRegRead (apci97_devc * devc, unsigned short reg)
{
  int i, dtemp;

  dtemp = INL (devc->osdev, devc->base + CONC_dSRCIO_OFF);
  /* wait for ready */
  for (i = 0; i < SRC_IOPOLL_COUNT; ++i)
    if (!
	((dtemp =
	  INL (devc->osdev, devc->base + CONC_dSRCIO_OFF)) & SRC_BUSY))
      break;

  /* assert a read request */
  OUTL (devc->osdev,
	(dtemp & SRC_CTLMASK) | ((int) reg << 25),
	devc->base + CONC_dSRCIO_OFF);

  /* now wait for the data */
  for (i = 0; i < SRC_IOPOLL_COUNT; ++i)
    if (!
	((dtemp =
	  INL (devc->osdev, devc->base + CONC_dSRCIO_OFF)) & SRC_BUSY))
      break;

  return (unsigned short) dtemp;
}


void
SRCRegWrite (apci97_devc * devc, unsigned short reg, unsigned short val)
{
  int i, dtemp;
  int writeval;

  dtemp = INL (devc->osdev, devc->base + CONC_dSRCIO_OFF);
  /* wait for ready */
  for (i = 0; i < SRC_IOPOLL_COUNT; ++i)
    if (!
	((dtemp =
	  INL (devc->osdev, devc->base + CONC_dSRCIO_OFF)) & SRC_BUSY))
      break;

  /* assert the write request */
  writeval = (dtemp & SRC_CTLMASK) | SRC_WENABLE | ((int) reg << 25) | val;
  OUTL (devc->osdev, writeval, devc->base + CONC_dSRCIO_OFF);

  return;
}

typedef struct
{
  unsigned char base;
  unsigned short rate;
}
SRC_RATE_REC;

#define SRC_RATE_RECS (3)
SRC_RATE_REC theSRCRates[SRC_RATE_RECS] = {
  {SRC_SYNTH_BASE, 0},
  {SRC_DAC_BASE, 0},
  {SRC_ADC_BASE, 0}
};

unsigned short
SRCGetRate (apci97_devc * devc, unsigned char base)
{
  unsigned short i;

  for (i = 0; i < SRC_RATE_RECS; i++)
    if (theSRCRates[i].base == base)
      return theSRCRates[i].rate;

  return 0;
}

void
SRCSetRate (apci97_devc * devc, unsigned char base, unsigned short rate)
{
  int i, freq, dtemp;
  unsigned short N, truncM, truncStart;


  for (i = 0; i < SRC_RATE_RECS; i++)
    if (theSRCRates[i].base == base)
      {
	theSRCRates[i].rate = rate;
	break;
      }

  if (base != SRC_ADC_BASE)
    {
      /* freeze the channel */
      dtemp = base == SRC_SYNTH_BASE ? SRC_SYNTHFREEZE : SRC_DACFREEZE;
      for (i = 0; i < SRC_IOPOLL_COUNT; ++i)
	if (!(INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & SRC_BUSY))
	  break;
      OUTL (devc->osdev,
	    (INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & SRC_CTLMASK) |
	    dtemp, devc->base + CONC_dSRCIO_OFF);

      /* calculate new frequency and write it - preserve accum */
      freq = ((int) rate << 16) / 3000U;
      SRCRegWrite (devc, (unsigned short) base + SRC_INT_REGS_OFF,
		   (SRCRegRead
		    (devc,
		     (unsigned short) base +
		     SRC_INT_REGS_OFF) & 0x00ffU) | ((unsigned short) (freq >>
								       6) &
						     0xfc00));
      SRCRegWrite (devc, (unsigned short) base + SRC_VFREQ_FRAC_OFF,
		   (unsigned short) freq >> 1);

      /* un-freeze the channel */
      for (i = 0; i < SRC_IOPOLL_COUNT; ++i)
	if (!(INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & SRC_BUSY))
	  break;
      OUTL (devc->osdev,
	    (INL (devc->osdev, devc->base + CONC_dSRCIO_OFF) & SRC_CTLMASK) &
	    ~dtemp, devc->base + CONC_dSRCIO_OFF);
    }
  else
    {
      /* derive oversample ratio */
      N = rate / 3000U;
      if (N == 15 || N == 13 || N == 11 || N == 9)
	--N;

      /* truncate the filter and write n/trunc_start */
      truncM = (21 * N - 1) | 1;
      if (rate >= 24000U)
	{
	  if (truncM > 239)
	    truncM = 239;
	  truncStart = (239 - truncM) >> 1;

	  SRCRegWrite (devc, base + SRC_TRUNC_N_OFF,
		       (truncStart << 9) | (N << 4));
	}
      else
	{
	  if (truncM > 119)
	    truncM = 119;
	  truncStart = (119 - truncM) >> 1;

	  SRCRegWrite (devc, base + SRC_TRUNC_N_OFF,
		       0x8000U | (truncStart << 9) | (N << 4));
	}

      /* calculate new frequency and write it - preserve accum */
      freq = ((48000UL << 16) / rate) * N;
      SRCRegWrite (devc, base + SRC_INT_REGS_OFF,
		   (SRCRegRead
		    (devc,
		     (unsigned short) base +
		     SRC_INT_REGS_OFF) & 0x00ff) | ((unsigned short) (freq >>
								      6) &
						    0xfc00));
      SRCRegWrite (devc, base + SRC_VFREQ_FRAC_OFF,
		   (unsigned short) freq >> 1);

      SRCRegWrite (devc, SRC_ADC_VOL_L, N << 8);
      SRCRegWrite (devc, SRC_ADC_VOL_R, N << 8);

    }

  return;
}

static void
apci97_writemem (apci97_devc * devc, int page, int offs, int data)
{
  int tmp;

  tmp = INL (devc->osdev, devc->base + 0xc);
  OUTL (devc->osdev, page, devc->base + 0xc);	/* Select memory page */
  OUTL (devc->osdev, data, devc->base + offs);
  OUTL (devc->osdev, tmp, devc->base + 0xc);	/* Select the original memory page */
}

static unsigned int
apci97_readmem (apci97_devc * devc, int page, int offs)
{
  unsigned int val;

  OUTL (devc->osdev, page, devc->base + 0xc);	/* Select memory page */
  val = INL (devc->osdev, devc->base + offs);
  return val;
}

static int
apci97intr (oss_device_t * osdev)
{
  int stats, i;
  int tmp;
  unsigned char ackbits = 0;
  unsigned char uart_stat;
  apci97_devc *devc = (apci97_devc *) osdev->devc;
  apci97_portc *portc;
  int served = 0;

  stats = INL (devc->osdev, devc->base + 0x04);
  /*cmn_err (CE_WARN, "AudioPCI97 intr status %08x\n", stats); */

  if (!(stats & 0x80000000))	/* No interrupt pending */
    return served;

  served = 1;

  for (i = 0; i < MAX_PORTC; i++)
    {
      portc = &devc->portc[i];

      if (stats & 0x00000010)	/* CCB interrupt */
	{
	  cmn_err (CE_WARN, "AudioPCI97: CCB interrupt\n");
	}

      if ((stats & 0x00000004) && (portc->atype))	/* DAC1 (synth) interrupt */
	{
	  ackbits |= CONC_SERCTL_DAC1IE;
	  if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    oss_audio_outputintr (portc->audiodev, 0);

	}

      if ((stats & 0x00000002) && (!portc->atype))	/* DAC2 interrupt */
	{
	  ackbits |= CONC_SERCTL_DAC2IE;
	  if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    oss_audio_outputintr (portc->audiodev, 0);
	}

      if ((stats & 0x00000001) && (!portc->atype))	/* ADC interrupt */
	{
	  ackbits |= CONC_SERCTL_ADCIE;
	  if (portc->trigger_bits & PCM_ENABLE_INPUT)
	    {
	      oss_audio_inputintr (portc->audiodev, 0);
	    }
	}

      if (stats & 0x00000008)	/* UART interrupt */
	{
	  uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF);

	  while (uart_stat & CONC_UART_RXRDY)
	    {
	      unsigned char d;
	      d = INB (devc->osdev, devc->base + CONC_bUARTDATA_OFF);

	      if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
		devc->midi_input_intr (devc->midi_dev, d);
	      uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF);
	    }

	}
      /* Ack the interrupt */
      tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF);
      OUTB (devc->osdev, (tmp & ~ackbits), devc->base + CONC_bSERCTL_OFF);	/* Clear bits */
      OUTB (devc->osdev, tmp | ackbits, devc->base + CONC_bSERCTL_OFF);	/* Return them back on */
    }

  return served;
}

/*
 * Audio routines
 */

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

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

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

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

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

  return portc->channels;
}

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

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


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

  return portc->bits;
}

static int
apci97_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static void apci97_audio_trigger (int dev, int state);

static void
apci97_audio_reset (int dev)
{
  apci97_audio_trigger (dev, 0);
}

static void
apci97_audio_reset_input (int dev)
{
  apci97_portc *portc = audio_engines[dev]->portc;
  apci97_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
apci97_audio_reset_output (int dev)
{
  apci97_portc *portc = audio_engines[dev]->portc;
  apci97_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

static int
apci97_audio_open (int dev, int mode, int open_flags)
{
  apci97_portc *portc = audio_engines[dev]->portc;
  apci97_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 -EBUSY;
    }
  portc->open_mode = mode;
  portc->audio_enabled = ~mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
apci97_audio_close (int dev, int mode)
{
  apci97_portc *portc = audio_engines[dev]->portc;

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

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

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

}

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

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

static void
apci97_audio_trigger (int dev, int state)
{
  apci97_devc *devc = audio_engines[dev]->devc;
  apci97_portc *portc = audio_engines[dev]->portc;
  int tmp;
  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))
	    {
	      if (portc->atype)
		{
		  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
		  tmp |= CONC_DEVCTL_DAC1_EN;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
		}
	      else
		{
		  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
		  tmp |= CONC_DEVCTL_DAC2_EN;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
		}
	      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;
	      if (portc->atype)
		{
		  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
		  tmp &= ~CONC_DEVCTL_DAC1_EN;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

		  tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF);
		  tmp &= ~CONC_SERCTL_DAC1IE;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
		}
	      else
		{
		  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
		  tmp &= ~CONC_DEVCTL_DAC2_EN;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

		  tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF);
		  tmp &= ~CONC_SERCTL_DAC2IE;
		  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);

		}
	    }
	}
    }

  if ((portc->open_mode & OPEN_READ)
      && !(audio_engines[dev]->flags & ADEV_NOINPUT))
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
	      tmp |= CONC_DEVCTL_ADC_EN;
	      OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
	      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;

	      tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF);
	      tmp &= ~CONC_DEVCTL_ADC_EN;
	      OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

	      tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF);
	      tmp &= ~CONC_SERCTL_ADCIE;
	      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
	    }
	}
    }

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static int
apci97_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  apci97_devc *devc = audio_engines[dev]->devc;
  apci97_portc *portc = audio_engines[dev]->portc;
  int tmp = 0x00;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  /* Set physical address of the DMA buffer */

  apci97_writemem (devc, CONC_ADCCTL_PAGE, CONC_dADCPADDR_OFF,
		   dmap->dmabuf_phys);

  /* Set ADC rate */
  SRCSetRate (devc, SRC_ADC_BASE, portc->speed);

  /* Set format */
  tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF);
  tmp &= ~(CONC_PCM_ADC_STEREO | CONC_PCM_ADC_16BIT);
  if (portc->channels == 2)
    tmp |= CONC_PCM_ADC_STEREO;
  if (portc->bits == 16)
    {
      tmp |= CONC_PCM_ADC_16BIT;
      OUTB (devc->osdev, 0x10, devc->base + CONC_bSKIPC_OFF);	/* Skip count register */
    }
  else
    {
      OUTB (devc->osdev, 0x08, devc->base + CONC_bSKIPC_OFF);	/* Skip count register */
    }
  OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF);

  /* Set the frame count */
  apci97_writemem (devc, CONC_ADCCTL_PAGE, CONC_wADCFC_OFF,
		   (dmap->bytes_in_use / 4) - 1);

  /* Set # of samples between interrupts */
  OUTW (devc->osdev,
	(dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1,
	devc->base + CONC_wADCIC_OFF);

  /* Enable the wave interrupt */
  tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_ADCIE;
  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
  tmp |= CONC_SERCTL_ADCIE;
  OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);

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

  return 0;
}

static int
apci97_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  apci97_devc *devc = audio_engines[dev]->devc;
  apci97_portc *portc = audio_engines[dev]->portc;
  int tmp;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (devc->revision >= 4)
    {
      /* set SPDIF to PCM mode */
      OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x1c) & ~0x2,
	    devc->base + 0x1c);
      if (portc->bits & AFMT_AC3)
	{
	  portc->channels = 2;
	  portc->bits = 16;
	  portc->speed = 48000;
	  /* set S/PDIF to AC3 Mode */
	  OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x1c) | 0x2,
		devc->base + 0x1c);
	}
    }

  if (portc->atype)
    {
      /* Set physical address of the DMA buffer */
      apci97_writemem (devc, CONC_SYNCTL_PAGE, CONC_dSYNPADDR_OFF,
		       dmap->dmabuf_phys);

      /* Set DAC1 rate */
      SRCSetRate (devc, SRC_SYNTH_BASE, portc->speed);

      /* Set format */
      tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF);
      tmp &= ~((CONC_PCM_DAC_STEREO | CONC_PCM_DAC_16BIT) >> 2);
      if (portc->channels == 2)
	tmp |= (CONC_PCM_DAC_STEREO >> 2);
      if (portc->bits == 16)
	{
	  tmp |= (CONC_PCM_DAC_16BIT >> 2);
	}
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF);

      /* Set the frame count */
      apci97_writemem (devc, CONC_SYNCTL_PAGE, CONC_wSYNFC_OFF,
		       (dmap->bytes_in_use / 4) - 1);

      /* Set # of samples between interrupts */
      OUTW (devc->osdev,
	    (dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1,
	    devc->base + CONC_wSYNIC_OFF);

      /* Enable the wave interrupt */
      tmp =
	INB (devc->osdev,
	     devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_DAC1IE;
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
      tmp |= CONC_SERCTL_DAC1IE;
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);

    }
  else
    {
      /* Set physical address of the DMA buffer */
      apci97_writemem (devc, CONC_DACCTL_PAGE, CONC_dDACPADDR_OFF,
		       dmap->dmabuf_phys);

      /* Set DAC rate */
      SRCSetRate (devc, SRC_DAC_BASE, portc->speed);

      /* Set format */
      tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF);
      tmp &= ~(CONC_PCM_DAC_STEREO | CONC_PCM_DAC_16BIT);
      if (portc->channels == 2)
	tmp |= CONC_PCM_DAC_STEREO;
      if (portc->bits == 16)
	{
	  tmp |= CONC_PCM_DAC_16BIT;
	  OUTB (devc->osdev, 0x10, devc->base + CONC_bSKIPC_OFF);	/* Skip count register */
	}
      else
	{
	  OUTB (devc->osdev, 0x08, devc->base + CONC_bSKIPC_OFF);	/* Skip count register */
	}
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF);


      /* Set the frame count */
      apci97_writemem (devc, CONC_DACCTL_PAGE, CONC_wDACFC_OFF,
		       (dmap->bytes_in_use / 4) - 1);

      /* Set # of samples between interrupts */
      OUTW (devc->osdev,
	    (dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1,
	    devc->base + CONC_wDACIC_OFF);

      /* Enable the wave interrupt */
      tmp =
	INB (devc->osdev,
	     devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_DAC2IE;
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
      tmp |= CONC_SERCTL_DAC2IE;
      OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF);
    }

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

static int
apci97_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  apci97_devc *devc = audio_engines[dev]->devc;
  apci97_portc *portc = audio_engines[dev]->portc;
  int ptr = 0, pos = 0, port = 0, page = 0;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_OUTPUT)
    {
      if (portc->atype)
	{
	  port = CONC_wSYNFC_OFF;
	  page = CONC_SYNCTL_PAGE;
	}
      else
	{
	  port = CONC_wDACFC_OFF;
	  page = CONC_DACCTL_PAGE;
	}
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      port = CONC_wADCFC_OFF;
      page = CONC_ADCCTL_PAGE;
    }

  ptr = apci97_readmem (devc, page, port);
  ptr >>= 16;
  ptr <<= 2;			/* count is in dwords */
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return ptr;
}

audiodrv_t apci97_audio_driver = {
  apci97_audio_open,
  apci97_audio_close,
  apci97_audio_output_block,
  apci97_audio_start_input,
  apci97_audio_ioctl,
  apci97_audio_prepare_for_input,
  apci97_audio_prepare_for_output,
  apci97_audio_reset,
  NULL,
  NULL,
  apci97_audio_reset_input,
  apci97_audio_reset_output,
  apci97_audio_trigger,
  apci97_audio_set_rate,
  apci97_audio_set_format,
  apci97_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* apci97_alloc_buffer */
  NULL,				/* apci97_free_buffer */
  NULL,
  NULL,
  apci97_get_buffer_pointer
};

static int
apci97_midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
		  oss_midi_inputbuf_t inputbuf,
		  oss_midi_outputintr_t outputintr)
{
  apci97_devc *devc = (apci97_devc *) midi_devs[dev]->devc;

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

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

  if (mode & OPEN_READ)
    {
      OUTB (devc->osdev, CONC_UART_RXINTEN, devc->base + CONC_bUARTCSTAT_OFF);
    }

  return 0;
}

static void
apci97_midi_close (int dev, int mode)
{
  apci97_devc *devc = (apci97_devc *) midi_devs[dev]->devc;

  OUTB (devc->osdev, 0x00, devc->base + CONC_bUARTCSTAT_OFF);
  devc->midi_opened = 0;
}

static int
apci97_midi_out (int dev, unsigned char midi_byte)
{
  apci97_devc *devc = (apci97_devc *) midi_devs[dev]->devc;
  int i;

  unsigned char uart_stat =
    INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF);

  i = 0;
  while (i < 1000000 && !(uart_stat & CONC_UART_TXRDY))
    {
      uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF);
      i++;
    }

  if (!(uart_stat & CONC_UART_TXRDY))
    return 0;


  OUTB (devc->osdev, midi_byte, devc->base + CONC_bUARTDATA_OFF);

  return 1;
}

static int
apci97_midi_ioctl (int dev, unsigned cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static midi_driver_t apci97_midi_driver = {
  apci97_midi_open,
  apci97_midi_close,
  apci97_midi_ioctl,
  apci97_midi_out
};

static int
apci97_control (int dev, int ctrl, unsigned int cmd, int value)
{
  apci97_devc *devc = mixer_devs[dev]->hw_devc;

  if (cmd == SNDCTL_MIX_READ)
    {
      value = 0;
      switch (ctrl)
	{
	case 1:		/* Speaker Mode */
	  value = (INL (devc->osdev, devc->base + 4) & (1 << 26) ? 1 : 0);
	  break;

	case 2:		/* Dual Dac Mode */
	  value = INL (devc->osdev, devc->base + 4) & (1 << 27) ? 1 : 0;
	  break;
	}
    }
  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (ctrl)
	{
	case 1:		/* Front/Rear Mirror */
	  if (value)
	    {
	      OUTL (devc->osdev,
		    INL (devc->osdev, devc->base + 4) | (1 << 26),
		    devc->base + 4);
	    }
	  else
	    {
	      OUTL (devc->osdev,
		    INL (devc->osdev, devc->base + 4) & ~(1 << 26),
		    devc->base + 4);
	    }
	  break;

	case 2:		/* DAC1->Front DAC2->REAR */
	  if (value)
	    {
	      /* disable front/rear mirroring */
	      OUTL (devc->osdev,
		    INL (devc->osdev, devc->base + 4) & ~(1 << 26),
		    devc->base + 4);
	      /* Enable Dual Dac mode */
	      OUTL (devc->osdev,
		    INL (devc->osdev, devc->base + 4) | (1 << 27) | (1 << 24),
		    devc->base + 4);
	    }
	  else
	    {
	      /* enable mirror */
	      OUTL (devc->osdev,
		    INL (devc->osdev, devc->base + 4) | (1 << 26),
		    devc->base + 4);
	      /* disable dual dac */
	      OUTL (devc->osdev,
		    INL (devc->osdev,
			 devc->base + 4) & ~((1 << 27) | (1 << 24)),
		    devc->base + 4);
	    }
	  break;
	}
    }
  return value;
}

static int
apci97_mix_init (int dev)
{
  int group, err;

  if ((group = mixer_ext_create_group (dev, 0, "MIXEXT")) < 0)
    return group;

  if ((err = mixer_ext_create_control (dev, group, 1, apci97_control,
				       MIXT_ENUM, "SPKMODE", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 2, apci97_control,
				       MIXT_ONOFF, "DUALDAC", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  return 0;

}

static int
init_apci97 (apci97_devc * devc, int device_id)
{
  int my_mixer;
  int tmp, i;
  int first_dev = 0;

  if ((device_id == ENSONIQ_ES5880) || (device_id == ENSONIQ_ES5880A) ||
      (device_id == ENSONIQ_ES5880B) ||
      (device_id == 0x1371 && devc->revision == 7) ||
      (device_id == 0x1371 && devc->revision >= 9))
    {
      int i;

      /* Have a ES5880 so enable the codec manually */
      tmp = INB (devc->osdev, devc->base + CONC_bINTSUMM_OFF) & 0xff;
      tmp |= 0x20;
      OUTB (devc->osdev, tmp, devc->base + CONC_bINTSUMM_OFF);	/* OUTB? */
      for (i = 0; i < 2000; i++)
	oss_udelay (10);
    }

  SRCInit (devc);
#if 0
  OUTB (devc->osdev, 0x00, devc->base + CONC_bSERCTL_OFF);
  OUTB (devc->osdev, 0x00, devc->base + CONC_bNMIENA_OFF);	/* NMI off */
  OUTB (devc->osdev, 0x00, devc->base + CONC_wNMISTAT_OFF);	/* OUTB? */
#endif
/*
 * Turn on UART and CODEC
 */
  tmp = INL (devc->osdev, devc->base + CONC_bDEVCTL_OFF) & 0xff;
  tmp &= ~(CONC_DEVCTL_PCICLK_DS | CONC_DEVCTL_XTALCLK_DS);
  OUTB (devc->osdev, tmp | CONC_DEVCTL_UART_EN | CONC_DEVCTL_JSTICK_EN,
	devc->base + CONC_bDEVCTL_OFF);
  OUTB (devc->osdev, 0x00, devc->base + CONC_bUARTCSTAT_OFF);

  /* Perform AC97 codec warm reset */
  tmp = INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF) & 0xff;
  OUTB (devc->osdev, tmp | CONC_MISCCTL_SYNC_RES,
	devc->base + CONC_bMISCCTL_OFF);
  oss_udelay (200);
  OUTB (devc->osdev, tmp, devc->base + CONC_bMISCCTL_OFF);
  oss_udelay (200);

/*
 * Enable S/PDIF
 */
  if (devc->revision >= 4)
    {
      if (apci_spdif)
	{
	  /* enable SPDIF */
	  OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x04) | (1 << 18),
		devc->base + 0x04);
	  /* SPDIF out = data from DAC */
	  OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x00) | (1 << 26),
		devc->base + 0x00);
	}
      else
	{
	  /* disable spdif out */
	  OUTL (devc->osdev,
		INL (devc->osdev, devc->base + 0x04) & ~(1 << 18),
		devc->base + 0x04);
	  OUTL (devc->osdev,
		INL (devc->osdev, devc->base + 0x00) & ~(1 << 26),
		devc->base + 0x00);
	}
    }

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

  if (my_mixer < 0)
    return 0;

  if (devc->revision >= 4)
    {
      /* enable 4 speaker mode */
      OUTL (devc->osdev, INL (devc->osdev, devc->base + 4) | (1 << 26),
	    devc->base + 4);
      mixer_ext_set_init_fn (my_mixer, apci97_mix_init, 5);
    }

  for (i = 0; i < MAX_PORTC; i++)
    {

      int adev;
      char tmp_name[100];
      apci97_portc *portc = &devc->portc[i];
      int caps = ADEV_AUTOMODE;
      int fmts = AFMT_U8 | AFMT_S16_LE;

      if (devc->revision >= 4)
	fmts |= AFMT_AC3;

      if (i == 0)
	{
	  sprintf (tmp_name, "%s (rev %d)", devc->chip_name, devc->revision);
	  caps |= ADEV_DUPLEX;
	}
      else
	{
	  sprintf (tmp_name, "%s (playback only)", devc->chip_name);
	  caps |= ADEV_NOINPUT;
	}

      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&apci97_audio_driver,
					sizeof (audiodrv_t),
					caps, fmts, NULL, -1)) < 0)
	{
	  adev = -1;
	  return 0;
	}
      else
	{
	  if (i == 0)
	    first_dev = adev;
	  audio_engines[adev]->devc = devc;
	  audio_engines[adev]->portc = portc;
	  audio_engines[adev]->rate_source = first_dev;
	  audio_engines[adev]->min_rate = 5000;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  portc->open_mode = 0;
	  portc->audiodev = adev;
	  portc->atype = i;
	}

      audio_engines[adev]->mixer_dev = my_mixer;
    }

  if ((devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "APCI97", "APCI97 UART", &apci97_midi_driver, sizeof (midi_driver_t), NULL,	/* &std_midi_synth, */
					     0, devc, devc->osdev)) < 0)
    {
      cmn_err (CE_WARN, "APCI97: Couldn't install MIDI device\n");
      return 0;
    }

  devc->midi_opened = 0;
  return 1;
}

int
apci97_attach (oss_device_t * osdev)
{
  char tmp[100];
  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  apci97_devc *devc;
  int err;

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

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

  if ((vendor != ENSONIQ_VENDOR_ID && vendor != ECTIVA_VENDOR_ID) ||
      (device != ENSONIQ_ES1371 && device != ENSONIQ_ES5880 &&
       device != ENSONIQ_ES5880A && device != ECTIVA_ES1938 &&
       device != ENSONIQ_ES5880B))

    return 0;

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


  if (pci_irq_line == 0)
    {
      cmn_err (CE_WARN, "AudioPCI97: IRQ not set (%d). Can't continue\n",
	       pci_irq_line);
      return 0;
    }

  if (pci_ioaddr == 0)
    {
      cmn_err (CE_WARN, "AudioPCI97: undefined I/O address.\n");
      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;

  switch (device)
    {
    case ENSONIQ_ES1371:
      devc->chip_name = "Creative AudioPCI97 (ES1371)";
      break;
    case ECTIVA_ES1938:
      devc->chip_name = "Ectiva AudioPCI";
      break;
    case ENSONIQ_ES5880:
    case ENSONIQ_ES5880A:
    case ENSONIQ_ES5880B:
      devc->chip_name = "Sound Blaster PCI128";
      break;
    default:
      devc->chip_name = "AudioPCI97";
    }

  devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);
  /* Remove I/O space marker in bit 0. */
  devc->base &= ~3;

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

  /* set the PCI latency to 32 */
  if ((apci_latency == 32) || (apci_latency == 64) || (apci_latency == 96))
    pci_write_config_byte (osdev, 0x0d, apci_latency);



  oss_register_device (osdev, devc->chip_name);

  if ((err = oss_register_interrupts (osdev, 0, apci97intr, NULL)) < 0)
    {
      cmn_err (CE_WARN, "AudioPCI97: 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);


  devc->revision = pci_revision;
  return init_apci97 (devc, device);	/* Detected */
}

int
apci97_detach (oss_device_t * osdev)
{
  apci97_devc *devc = (apci97_devc *) osdev->devc;
  int tmp;

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

  tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF) &
    ~(CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_ADC_EN | CONC_DEVCTL_DAC1_EN);
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);
  OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF);

  oss_unregister_interrupts (devc->osdev);

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

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