/*
 * Purpose: Driver for ESS Maestro1/2 (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 "maestro_cfg.h"
#include "oss_pci.h"
#include "uart401.h"
#include "ac97.h"

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

#define BYTE unsigned char
#define WORD unsigned short
#define DWORD unsigned int
#define HIWORD(dw) ((dw >> 16) & 0xffff)
#define LOWORD(dw) (dw & 0xffff)
#define HIBYTE(w) ((w >> 8) & 0xff)
#define LOBYTE(w) (w & 0xff)

#define gwPTBaseIO devc->base
#define outpw(p,d) OUTW(devc->osdev, d, p)

#define DISABLE 0
#define ENABLE  1

#define CHANNEL0    0x0
#define bAPURPlay   0x0
#define bAPULPlay   0x1

#define bAPULSrc  	0x2
#define bAPURSrc  	0x3
#define bAPULMix  	0x4
#define bAPURMix  	0x5


#define ESS_VENDOR_ID	0x1285
#define ESS_MAESTRO		0x0100
#define ESS2_VENDOR_ID	0x125d
#define ESS_MAESTRO2	0x1968
#define ESS_MAESTRO2E 	0x1978

#define WSETBIT(w, b) (w | (1 << b))
#define WMSKBIT(w, b) (w & ~(1 << b))
#define DWSETBIT(w, b) (w | (1 << b))
#define DWMSKBIT(w, b) (w & ~(1 << b))

static int SYSCLK;
#define ESSM_CFMT_STEREO     0x01
#define ESSM_CFMT_16BIT      0x02
#define ESSM_CFMT_MASK       0x03
#define ESSM_CFMT_ASHIFT     0
#define ESSM_CFMT_CSHIFT     4

#define MAXCOUNTDOWN 31
DWORD dwFrequencyTable[] = {
  512, 1024, 2048, 4096, 8192, 16384, 32768, 65536
};

#define IO_PT_CODEC_CMD     ( devc->base + 0x30 )
#define IO_PT_CODEC_STATUS  ( devc->base + 0x30 )
#define IO_PT_CODEC_DATA    ( devc->base + 0x32 )
#define IO_PT_CODEC_FORMATA  ( devc->base + 0x34 )
#define IO_PT_CODEC_FORMATB  ( devc->base + 0x36 )

#define _DST_MONO           0x00
#define _DST_STEREO         0x08

#define _DST_NONE           0x00
#define _DST_DAC            0x01
#define _DST_MODEM          0x02
#define _DST_RESERVED1      0x03
#define _DST_DIRECTSOUND    0x04
#define _DST_ASSP           0x05
#define _DST_RESERVED2      0x06
#define _DST_RESERVED3      0x07

#define PT101_MIXER_DEVS         (SOUND_MASK_LINE1 | SOUND_MASK_LINE2 | \
                                 SOUND_MASK_MIC | SOUND_MASK_VOLUME | \
                                 SOUND_MASK_LINE3 | \
				 SOUND_MASK_SYNTH | SOUND_MASK_CD | \
			         SOUND_MASK_LINE | SOUND_MASK_PCM | \
			         SOUND_MASK_IGAIN)

#define PT101_MIXER_RECDEVS      (SOUND_MASK_LINE | SOUND_MASK_MIC | \
				 SOUND_MASK_CD | SOUND_MASK_LINE1 | \
				 SOUND_MASK_LINE2 | SOUND_MASK_LINE3)

#define PT101_MIXER_STEREODEVS   (SOUND_MASK_LINE | SOUND_MASK_IGAIN | \
                                 SOUND_MASK_VOLUME | SOUND_MASK_SYNTH | \
                                 SOUND_MASK_CD |  \
                                 SOUND_MASK_PCM | SOUND_MASK_LINE1 | \
				 SOUND_MASK_LINE2 | SOUND_MASK_LINE3)

static int default_mixer_levels[32] = {
  0x3232,			/* Master Volume */
  0x3232,			/* Bass */
  0x3232,			/* Treble */
  0x4b4b,			/* FM */
  0x3232,			/* PCM */
  0x1515,			/* PC Speaker */
  0x2020,			/* Ext Line */
  0x1010,			/* Mic */
  0x4b4b,			/* CD */
  0x0000,			/* Recording monitor */
  0x4b4b,			/* Second PCM */
  0x4b4b,			/* Recording level */
  0x4b4b,			/* Input gain */
  0x4b4b,			/* Output gain */
  0x2020,			/* Line1 */
  0x2020,			/* Line2 */
  0x1515			/* Line3 (usually line in) */
};

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

#define MAX_PORTC 2

typedef struct maestro_devc
{
  oss_device_t *osdev;
  oss_native_word base;
  int irq;
  int model;
  char *chip_name;
  int open_mode;
  /* Data table for virtualizing write-only registers */
  WORD wIDRRegDataTable[0x1F];
  WORD gwWCRegTable[0x200];
#define MD_MAESTRO1		1
#define MD_MAESTRO2		2
#define MD_MAESTRO2E		3
  unsigned short gpio;
  oss_mutex_t lock;
  oss_mutex_t low_lock;

  struct dmabuf
  {
    unsigned char *rawbuf;
    unsigned dmasize;
    oss_native_word base;	/* Offset for ptr */
  }
  dma_adc, dma_dac, dma_mix;
  int wApuBufferSize;
  unsigned char *dmapages;
  oss_native_word dmapages_phys;
  int dmalen;

  /* Mixer parameters */
  ac97_devc ac97devc;
  /* PT101 Mixer parameters */
  int my_mixer;
  int *levels;
  int recdevs;
  int recmask;

  maestro_portc portc[MAX_PORTC];
}
maestro_devc;

extern WORD wRdAPUReg (maestro_devc * devc, BYTE _bChannel, BYTE _bRegIndex);

static int
maestrointr (oss_device_t * osdev)
{
  maestro_devc *devc = (maestro_devc *) osdev->devc;
  maestro_portc *portc;
  int i;
  int serviced = 0;

  if (!(INW (devc->osdev, devc->base + 0x1A)))
    return 0;

  OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x04) | 0x1,
	devc->base + 0x04);
  serviced = 1;

  /* ack all interrupts */
  OUTB (devc->osdev, 0xFF, devc->base + 0x1A);
  for (i = 0; i < MAX_PORTC; i++)
    {
      portc = &devc->portc[i];

      /* Handle Playback */
      if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	{
	  dmap_t *dmapout = audio_engines[portc->audiodev]->dmap_out;
	  WORD wApuCurrentPos;
	  int n;

	  wApuCurrentPos = wRdAPUReg (devc, bAPURPlay, 0x05);
	  wApuCurrentPos = (wApuCurrentPos - devc->dma_dac.base) & 0xFFFE;
	  wApuCurrentPos = (wApuCurrentPos % devc->wApuBufferSize) << 1;
	  wApuCurrentPos /= dmapout->fragment_size;	/*Actual qhead */
	  if (wApuCurrentPos == 0 || wApuCurrentPos > dmapout->nfrags)
	    wApuCurrentPos = 0;
	  n = 0;
	  while (dmap_get_qhead (dmapout) != wApuCurrentPos
		 && n++ < dmapout->nfrags)
	    oss_audio_outputintr (portc->audiodev, 0);
	}

      /* Handle recording */
      if (portc->trigger_bits & PCM_ENABLE_INPUT)
	{
	  dmap_t *dmapin = audio_engines[portc->audiodev]->dmap_in;
	  WORD wApuCurrentPos;
	  int n;

	  wApuCurrentPos = wRdAPUReg (devc, bAPULSrc, 0x05);
	  wApuCurrentPos = (wApuCurrentPos - devc->dma_adc.base) & 0xFFFE;
	  wApuCurrentPos = (wApuCurrentPos % devc->wApuBufferSize) << 1;
	  wApuCurrentPos /= dmapin->fragment_size;	/* Actual qtail */
	  if (wApuCurrentPos == 0 || wApuCurrentPos > dmapin->nfrags)
	    wApuCurrentPos = 0;
	  n = 0;
	  while (dmap_get_qtail (dmapin) != wApuCurrentPos
		 && n++ < dmapin->nfrags)
	    oss_audio_inputintr (portc->audiodev, 0);
	}
    }
  return serviced;
}

static int
ac97_read (void *devc_, int addr)
{
  maestro_devc *devc = devc_;
  int data, i;
  oss_native_word flags;
  int sanity = 10000;

  MUTEX_ENTER_IRQDISABLE (devc->low_lock, flags);

  for (i = 0; i < 100000; i++)
    if (!(INB (devc->osdev, devc->base + 0x30) & 0x01))
      break;
  OUTW (devc->osdev, addr | 0x80, devc->base + 0x30);

  while (INB (devc->osdev, devc->base + 0x30) & 1)
    {
      sanity--;
      if (!sanity)
	{
	  cmn_err (CE_WARN, "ac97 codec timeout - 0x%x.\n", addr);
	  MUTEX_EXIT_IRQRESTORE (devc->low_lock, flags);
	  return 0;
	}
    }

  data = INW (devc->osdev, devc->base + 0x32);
  oss_udelay (100);

  MUTEX_EXIT_IRQRESTORE (devc->low_lock, flags);
  return data & 0xffff;
}

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

  MUTEX_ENTER_IRQDISABLE (devc->low_lock, flags);

  for (i = 0; i < 10000; i++)
    if (!(INB (devc->osdev, devc->base + 0x30) & 0x01))
      break;
  OUTW (devc->osdev, data & 0xffff, devc->base + 0x32);
  oss_udelay (100);
  OUTW (devc->osdev, (addr & 0x7f) & ~0x80, devc->base + 0x30);
  oss_udelay (100);

  MUTEX_EXIT_IRQRESTORE (devc->low_lock, flags);

  return 0;
}

/****************************************************
 *		PT101 CODEC Routines						*
 ****************************************************/

/************************************************************************/
/*                     PT101 CODEC Read									*/
/************************************************************************/

static int
pt101_set_recmask (maestro_devc * devc, int mask)
{
  int bits = 0;
  mask &= devc->recmask;

  mask = mask & (devc->recdevs ^ mask);	/* Pick the one recently turned on */

  if (!mask)
    return devc->recdevs;

  devc->recdevs = mask;

  switch (mask)
    {
    case SOUND_MASK_LINE:
      bits = 0x0000;
      break;
    case SOUND_MASK_MIC:
      bits = 0x0020;
      break;
    case SOUND_MASK_CD:
    case SOUND_MASK_LINE1:
      bits = 0x0040;
      break;
     /*CD*/ case SOUND_MASK_LINE2:
      bits = 0x0060;
      break;			/*Video */
    case SOUND_MASK_LINE3:
      bits = 0x0080;
      break;			/*Modem */
    default:			/* Unknown bit (combination) */
      mask = SOUND_MASK_MIC;
      bits = 0x0020;
    }

  ac97_write (devc, 0x02, bits);

  return devc->levels[31] = devc->recdevs;
}

static int
pt101_mixer_get (maestro_devc * devc, int dev)
{
  if (!((1 << dev) & PT101_MIXER_DEVS))
    return -EINVAL;

  return devc->levels[dev];
}

static int
pt101_mixer_set (maestro_devc * devc, int dev, int value)
{

  int left, right, lvl;
  int lch, rch;
  static const char pt101_mix_map[101] = {
    15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13,
    12, 12,
    12, 12, 12, 12, 11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 9, 9, 9,
    9,
    9, 9, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6,
    5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 2, 2,
    2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  };


  if (!((1 << dev) & PT101_MIXER_DEVS))
    return -EINVAL;

  if (!((1 << dev) & PT101_MIXER_STEREODEVS))
    {
      lvl = value & 0xff;
      if (lvl > 100)
	lvl = 100;
      lch = rch = lvl;
      value = lvl | (lvl << 8);
    }
  else
    {
      lch = value & 0xff;
      rch = (value >> 8) & 0xff;
      if (lch > 100)
	lch = 100;
      if (rch > 100)
	rch = 100;
      value = lch | (rch << 8);
    }

/* Now adjust the left and right channel to the mixer map*/
  left = pt101_mix_map[lch];
  right = pt101_mix_map[rch];

  switch (dev)
    {
    case SOUND_MIXER_CD:
    case SOUND_MIXER_LINE1:
      if (rch == 0)
	ac97_write (devc, 0x04, (ac97_read (devc, 0x04) & 0xFF7F) | 0x0080);
      else
	ac97_write (devc, 0x04, (ac97_read (devc, 0x04) & 0xFF7F));

      if (lch == 0)
	ac97_write (devc, 0x04, (ac97_read (devc, 0x04) & 0x7FFF) | 0x8000);
      else
	ac97_write (devc, 0x04, (ac97_read (devc, 0x04) & 0x7FFF));

      ac97_write (devc, 0x04, ((ac97_read (devc, 0x04) & 0xFFE1) | (right << 1)));	/*Right */
      ac97_write (devc, 0x04, ((ac97_read (devc, 0x04) & 0xE1FF) | (left << 9)));	/*Left */
      break;

    case SOUND_MIXER_LINE2:
      if (rch == 0)
	ac97_write (devc, 0x05, (ac97_read (devc, 0x05) & 0xFF7F) | 0x0080);
      else
	ac97_write (devc, 0x05, (ac97_read (devc, 0x05) & 0xFF7F));

      if (lch == 0)
	ac97_write (devc, 0x05, (ac97_read (devc, 0x05) & 0x7FFF) | 0x8000);
      else
	ac97_write (devc, 0x05, (ac97_read (devc, 0x05) & 0x7FFF));

      ac97_write (devc, 0x05, (WORD) ((ac97_read (devc, 0x05) & 0xFFE1) | ((WORD) right << 1)));	/*Right */
      ac97_write (devc, 0x05, (WORD) ((ac97_read (devc, 0x05) & 0xE1FF) | ((WORD) left << 9)));	/*Left */
      break;

    case SOUND_MIXER_LINE3:
      if (rch == 0)
	ac97_write (devc, 0x06, (ac97_read (devc, 0x06) & 0xFF7F) | 0x0080);
      else
	ac97_write (devc, 0x06, (ac97_read (devc, 0x06) & 0xFF7F));

      if (lch == 0)
	ac97_write (devc, 0x06, (ac97_read (devc, 0x06) & 0x7FFF) | 0x8000);
      else
	ac97_write (devc, 0x06, (ac97_read (devc, 0x06) & 0x7FFF));

      ac97_write (devc, 0x06, (WORD) ((ac97_read (devc, 0x06) & 0xFFE1) | ((WORD) right << 1)));	/*Right */
      ac97_write (devc, 0x06, (WORD) ((ac97_read (devc, 0x06) & 0xE1FF) | ((WORD) left << 9)));	/*Left */
      break;

    case SOUND_MIXER_LINE:
      if (rch == 0)
	ac97_write (devc, 0x1D, (ac97_read (devc, 0x1D) & 0xFF7F) | 0x0080);
      else
	ac97_write (devc, 0x1D, (ac97_read (devc, 0x1D) & 0xFF7F));

      if (lch == 0)
	ac97_write (devc, 0x1D, (ac97_read (devc, 0x1D) & 0x7FFF) | 0x8000);
      else
	ac97_write (devc, 0x1D, (ac97_read (devc, 0x1D) & 0x7FFF));

      ac97_write (devc, 0x1D, (WORD) ((ac97_read (devc, 0x1D) & 0xFFE1) | ((WORD) right << 1)));	/*Right */
      ac97_write (devc, 0x1D, (WORD) ((ac97_read (devc, 0x1D) & 0xE1FF) | ((WORD) left << 9)));	/*Left */
      break;

    case SOUND_MIXER_MIC:
      if (rch == 0)
	ac97_write (devc, 0x07, (ac97_read (devc, 0x07) & 0xFF7F) | 0x0080);
      else
	ac97_write (devc, 0x07, (ac97_read (devc, 0x07) & 0xFF7F));

      if (lch == 0)
	ac97_write (devc, 0x07, (ac97_read (devc, 0x07) & 0x7FFF) | 0x8000);
      else
	ac97_write (devc, 0x07, (ac97_read (devc, 0x07) & 0x7FFF));

      left = right;
      ac97_write (devc, 0x07,
		  (WORD) ((ac97_read (devc, 0x07) & 0xFFE1) |
			  ((WORD) right << 1)));
      ac97_write (devc, 0x07,
		  (WORD) ((ac97_read (devc, 0x07) & 0xE1FF) |
			  ((WORD) right << 9)));
      break;

    case SOUND_MIXER_SYNTH:
    case SOUND_MIXER_VOLUME:
    case SOUND_MIXER_PCM:
      if (rch == 0)
	ac97_write (devc, 0x09, (ac97_read (devc, 0x09) & 0xFF7F) | 0x0080);
      else
	ac97_write (devc, 0x09, (ac97_read (devc, 0x09) & 0xFF7F));

      if (lch == 0)
	ac97_write (devc, 0x09, (ac97_read (devc, 0x09) & 0x7FFF) | 0x8000);
      else
	ac97_write (devc, 0x09, (ac97_read (devc, 0x09) & 0x7FFF));

      ac97_write (devc, 0x09, (WORD) ((ac97_read (devc, 0x09) & 0xFFE1) | ((WORD) right << 1)));	/*Right */
      ac97_write (devc, 0x09, (WORD) ((ac97_read (devc, 0x09) & 0xE1FF) | ((WORD) left << 9)));	/*Left */
      break;

    case SOUND_MIXER_IGAIN:
      ac97_write (devc, 0x02, (WORD) ((ac97_read (devc, 0x02) & 0xFFF0) | ((WORD) right)));	/*Right */
      ac97_write (devc, 0x02, (WORD) ((ac97_read (devc, 0x02) & 0xF0FF) | ((WORD) left << 8)));	/*Left */
      break;
    }

  return devc->levels[dev] = value;
}

static void
pt101_mixer_reset (maestro_devc * devc)
{
  int i;

  devc->levels = load_mixer_volumes ("ESS PT101", default_mixer_levels, 1);
  devc->recmask = PT101_MIXER_RECDEVS & PT101_MIXER_DEVS;
  for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
    pt101_mixer_set (devc, i, devc->levels[i]);
  pt101_set_recmask (devc, SOUND_MASK_MIC);
}

/*ARGSUSED*/
static int
pt101_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
{
  maestro_devc *devc = mixer_devs[dev]->devc;
  int val;

  if (((cmd >> 8) & 0xff) == 'M')
    {
      if (IOC_IS_OUTPUT (cmd))
	switch (cmd & 0xff)
	  {
	  case SOUND_MIXER_RECSRC:
	    val = *arg;
	    return *arg = pt101_set_recmask (devc, val);
	    break;

	  default:
	    val = *arg;
	    return *arg = pt101_mixer_set (devc, cmd & 0xff, val);
	  }
      else
	switch (cmd & 0xff)
	  {

	  case SOUND_MIXER_RECSRC:
	    return *arg = devc->recdevs;
	    break;

	  case SOUND_MIXER_DEVMASK:
	    return *arg = PT101_MIXER_DEVS;
	    break;

	  case SOUND_MIXER_STEREODEVS:
	    return *arg = PT101_MIXER_STEREODEVS;
	    break;

	  case SOUND_MIXER_RECMASK:
	    return *arg = devc->recmask;
	    break;

	  case SOUND_MIXER_CAPS:
	    return *arg = SOUND_CAP_EXCL_INPUT;
	    break;

	  default:
	    return *arg = pt101_mixer_get (devc, cmd & 0xff);
	  }
    }
  else
    return -EINVAL;
}

/*
 * Low level I/O routines
 */
#define WAVE_CACHE_INDEX    (devc->base + 0x10)
#define WAVE_CACHE_DATA         (devc->base + 0x12)
#define WAVE_CACHE_CONTROL  (devc->base + 0x14)

/*****************************************************************************/
/* Write Wave Cache Address I/O Port                                         */
/*                                                                           */
/*****************************************************************************/
static void
vWrWCRegIndex (maestro_devc * devc, WORD _wRegIndex)
{
  OUTW (devc->osdev, LOWORD (_wRegIndex), WAVE_CACHE_INDEX);
}

/*****************************************************************************/
/* Write Wave Cache Data I/O Port                                            */
/*                                                                           */
/*****************************************************************************/
static void
vWrWCRegData (maestro_devc * devc, WORD _wRegData)
{
  OUTW (devc->osdev, _wRegData, WAVE_CACHE_DATA);
}

/*****************************************************************************/
/* Read Wave Cache Addr I/O Port                                             */
/*                                                                           */
/*****************************************************************************/
WORD
wRdWCRegIndex (maestro_devc * devc)
{
  return ((WORD) INW (devc->osdev, WAVE_CACHE_INDEX));
}

/*****************************************************************************/
/* Read Wave Cache Data I/O Port                                             */
/*                                                                           */
/*****************************************************************************/
WORD
wRdWCRegData (maestro_devc * devc)
{
  return ((WORD) (INW (devc->osdev, WAVE_CACHE_DATA)));
}

/*****************************************************************************/
/* Write Wave Cache Memory                                                   */
/*                                                                           */
/*****************************************************************************/
void
vWrWCReg (maestro_devc * devc, WORD _wRegIndex, WORD _wRegData)
{
  vWrWCRegIndex (devc, _wRegIndex);
  vWrWCRegData (devc, _wRegData);
}

/*****************************************************************************/
/* Read Wave Cache Memory                                                    */
/*                                                                           */
/*****************************************************************************/
WORD
wRdWCReg (maestro_devc * devc, WORD _wRegIndex)
{
  WORD wRetVal;

  vWrWCRegIndex (devc, _wRegIndex);
  wRetVal = wRdWCRegData (devc);

  return (wRetVal);
}

/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*****************************************************************************/
void
vSetWCChannelControlBit (maestro_devc * devc, BYTE _bChannel, BYTE _bBit)
{
  vWrWCReg (devc, (WORD) (_bChannel << 3),
	    WSETBIT (wRdWCReg (devc, (WORD) (_bChannel << 3)), _bBit));
}

/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*****************************************************************************/
void
vMskWCChannelControlBit (maestro_devc * devc, BYTE _bChannel, BYTE _bBit)
{

  vWrWCReg (devc, (WORD) (_bChannel << 3),
	    WMSKBIT (wRdWCReg (devc, (WORD) (_bChannel << 3)), _bBit));
}


/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*****************************************************************************/
void
vSetWCChannelStereo (maestro_devc * devc, BYTE _bChannel, BYTE _bState)
{
  switch (_bState)
    {
    case 0:
       /*MONO*/ vMskWCChannelControlBit (devc, _bChannel, 1);
      break;

    case 1:
       /*STEREO*/ vSetWCChannelControlBit (devc, _bChannel, 1);
      break;
    }
}

/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*****************************************************************************/
void
vSetWCChannelWordSize (maestro_devc * devc, BYTE _bChannel, BYTE _bState)
{
  switch (_bState)
    {
    case 0:			/* 8BIT */
      vSetWCChannelControlBit (devc, _bChannel, 2);
      break;

    case 1:			/* 16BIT */
      vMskWCChannelControlBit (devc, _bChannel, 2);
      break;
    }
}

/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*****************************************************************************/
void
vSetWCChannelTagAddr (maestro_devc * devc, BYTE _bChannel, WORD _wTagAddr)
{
  vWrWCReg (devc, (WORD) (_bChannel << 3),
	    (WORD) ((wRdWCReg (devc, (WORD) (_bChannel << 3)) & 0x0007) |
		    (WORD) (_wTagAddr << 3)));
}

#define WAVERAM_START   0x400000

#define IDR0_DATA_PORT          0x00
#define IDR1_CRAM_POINTER       0x01
#define IDR2_CRAM_INCREMENT     0x02
#define IDR3_WAVE_DATA          0x03
#define IDR4_WAVE_PTR_LO        0x04
#define IDR5_WAVE_PTR_HI        0x05
#define IDR6_TIMER_CTRL         0x06
#define IDR7_WAVE_ROMRAM        0x07

/* Read/Write access for Indexed Data Registers */
#define _RW             0	/* Read/Write */
#define _WO             1	/* Write Only */
#define _RO             2	/* Read Only */

static BYTE bIDRAccessTable[] = {
  _RW, _RW, _RW, _RW,
  _RW, _RW, _WO, _WO,
  _WO, _WO, _WO, _WO,
  _WO, _WO, _WO, _WO,
  _WO, _WO, _RW, _WO,
  _RO, _RW, _RW, _WO
};

#define PT_DATA_PORT            devc->base
#define PT_INDEX_PORT           (devc->base + 0x02)

/*****************************************************************************/
/* Write IDR Register Index                                                  */
/*                                                                           */
/*****************************************************************************/
static void
vWrIDRIndex (maestro_devc * devc, WORD _wRegIndex)
{
  OUTW (devc->osdev, _wRegIndex, PT_INDEX_PORT);
}

/*****************************************************************************/
/* Write IDR Register Data                                                   */
/*                                                                           */
/*****************************************************************************/
static void
vWrIDRData (maestro_devc * devc, WORD _wRegData)
{
  OUTW (devc->osdev, _wRegData, PT_DATA_PORT);
}

/*****************************************************************************/
/* Read IDR Register Data                                                    */
/*                                                                           */
/*****************************************************************************/
static WORD
wRdIDRData (maestro_devc * devc)
{
  return ((WORD) INW (devc->osdev, PT_DATA_PORT));
}

/*****************************************************************************/
/* Write IDR Register                                                        */
/*                                                                           */
/*****************************************************************************/
static void
vWrIDR (maestro_devc * devc, WORD _wRegIndex, WORD _wRegData)
{
  vWrIDRIndex (devc, _wRegIndex);

  vWrIDRData (devc, _wRegData);

  devc->wIDRRegDataTable[_wRegIndex] = _wRegData;
}

/*****************************************************************************/
/* Read IDR Register                                                         */
/*                                                                           */
/*****************************************************************************/
static WORD
wRdIDR (maestro_devc * devc, WORD _wRegIndex)
{
  WORD wRetVal = 0;

  vWrIDRIndex (devc, _wRegIndex);

  switch (bIDRAccessTable[_wRegIndex])
    {
    case _RW:
    case _RO:
      wRetVal = wRdIDRData (devc);
      break;

    case _WO:
      wRetVal = devc->wIDRRegDataTable[_wRegIndex];
      break;
    }

  return (wRetVal);
}

/*****************************************************************************/
/* Set IDR Register Bit                                                      */
/*                                                                           */
/*****************************************************************************/
static void
vSetIDRBit (maestro_devc * devc, WORD _wRegIndex, BYTE _bRegBit)
{
  vWrIDR (devc, _wRegIndex, WSETBIT (wRdIDR (devc, _wRegIndex), _bRegBit));
}

/*****************************************************************************/
/* Mask IDR Register Bit                                                     */
/*                                                                           */
/*****************************************************************************/
static void
vMskIDRBit (maestro_devc * devc, WORD _wRegIndex, BYTE _bRegBit)
{
  vWrIDR (devc, _wRegIndex, WMSKBIT (wRdIDR (devc, _wRegIndex), _bRegBit));
}

/**************************/
#define _APU_MIXER  0x06
#define _APU_SYNR   0x3C
#define _APU_SYNL   0x3D
#define _APU_DIGL   0x3E
#define _APU_DIGR   0x3F

/* APU Modes */
#define	_APU_OFF			0x00
#define	_APU_16BITLINEAR	0x01	/* 16-Bit Linear Sample Player */
#define	_APU_16BITSTEREO	0x02	/* 16-Bit Stereo Sample Player */
#define	_APU_8BITLINEAR		0x03	/* 8-Bit Linear Sample Player */
#define	_APU_8BITSTEREO		0x04	/* 8-Bit Stereo Sample Player */
#define	_APU_8BITDIFF		0x05	/* 8-Bit Differential Sample Playrer */
#define	_APU_DIGITALDELAY	0x06	/* Digital Delay Line */
#define	_APU_DUALTAP		0x07	/* Dual Tap Reader */
#define	_APU_CORRELATOR		0x08	/* Correlator */
#define	_APU_INPUTMIXER		0x09	/* Input Mixer */
#define	_APU_WAVETABLE		0x0A	/* Wave Table Mode */
#define	_APU_SRCONVERTOR	0x0B	/* Sample Rate Convertor */
#define	_APU_16BITPINGPONG	0x0C	/* 16-Bit Ping-Pong Sample Player */

#define	_APU_RESERVED1		0x0D	/* Reserved 1 */
#define	_APU_RESERVED2		0x0E	/* Reserved 2 */
#define	_APU_RESERVED3		0x0F	/* Reserved 3 */

/* APU Filtey Q Control */
#define _APU_FILTER_LESSQ           0x00
#define _APU_FILTER_MOREQ           0x03

/* APU Filter Control */
#define	_APU_FILTER_2POLE_LOPASS	0x00
#define	_APU_FILTER_2POLE_BANDPASS	0x01
#define	_APU_FILTER_2POLE_HIPASS	0x02
#define	_APU_FILTER_1POLE_LOPASS	0x03
#define	_APU_FILTER_1POLE_HIPASS	0x04
#define	_APU_FILTER_OFF				0x05

/* Polar Pan Control */
#define	_APU_PAN_CENTER_CIRCLE		0x00
#define	_APU_PAN_MIDDLE_RADIUS		0x01
#define	_APU_PAN_OUTSIDE_RADIUS		0x02
#define	_APU_PAN_

/* APU ATFP Type */
#define	_APU_ATFP_AMPLITUDE			0x00
#define	_APU_ATFP_TREMELO			0x01
#define	_APU_ATFP_FILTER			0x02
#define	_APU_ATFP_PAN				0x03

/* APU ATFP Flags */
#define	_APU_ATFP_FLG_OFF			0x00
#define	_APU_ATFP_FLG_WAIT			0x01
#define	_APU_ATFP_FLG_DONE			0x02
#define	_APU_ATFP_FLG_INPROCESS		0x03

static WORD
wRdAPURegIndex (maestro_devc * devc)
{
  return (wRdIDR (devc, IDR1_CRAM_POINTER));
}

static void
vWrAPURegIndex (maestro_devc * devc, WORD _wRegIndex)
{
  vWrIDR (devc, IDR1_CRAM_POINTER, _wRegIndex);

  while (wRdAPURegIndex (devc) != _wRegIndex)
    {
    }
}

static WORD
wRdAPURegData (maestro_devc * devc)
{
  return (wRdIDR (devc, IDR0_DATA_PORT));
}

static void
vWrAPURegData (maestro_devc * devc, WORD _wRegData)
{
  while (wRdAPURegData (devc) != _wRegData)
    {
      vWrIDR (devc, IDR0_DATA_PORT, _wRegData);
    }
}

static void
vWrAPUReg (maestro_devc * devc, BYTE _bChannel, BYTE _bRegIndex,
	   WORD _wRegData)
{
  vWrAPURegIndex (devc, (WORD) ((_bChannel << 4) + _bRegIndex));
  vWrAPURegData (devc, _wRegData);
}

/* Should be static */ WORD
wRdAPUReg (maestro_devc * devc, BYTE _bChannel, BYTE _bRegIndex)
{
  vWrAPURegIndex (devc, (WORD) ((_bChannel << 4) + _bRegIndex));
  return (wRdAPURegData (devc));
}

/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*****************************************************************************/
DWORD
wCalculateAPUFrequency (DWORD freq)
{
  if (freq == 48000)
    return 0x10000;

  return ((freq / (SYSCLK / 1024L)) << 16) +
    (((freq % (SYSCLK / 1024L)) << 16) / (SYSCLK / 1024L));
}


static void
vSetAPUFrequency (maestro_devc * devc, BYTE _bChannel, DWORD _dwFrequency)
{
  vWrAPUReg (devc, _bChannel, 0x02,
	     (WORD) ((wRdAPUReg (devc, _bChannel, 0x02) & 0x00FF) |
		     (WORD) (LOBYTE (_dwFrequency) << 8)));
  vWrAPUReg (devc, _bChannel, 0x03, (WORD) (_dwFrequency >> 8));
}

static void
vSetAPURegBit (maestro_devc * devc, BYTE _bChannel, BYTE _bRegIndex,
	       BYTE _bRegBit)
{
  vWrAPUReg (devc, _bChannel, _bRegIndex,
	     WSETBIT (wRdAPUReg (devc, _bChannel, _bRegIndex), _bRegBit));
}

static void
vMskAPURegBit (maestro_devc * devc, BYTE _bChannel, BYTE _bRegIndex,
	       BYTE _bRegBit)
{
  vWrAPUReg (devc, _bChannel, _bRegIndex,
	     WMSKBIT (wRdAPUReg (devc, _bChannel, _bRegIndex), _bRegBit));
}

static void
vSetAPUSubmixMode (maestro_devc * devc, BYTE _bChannel, BYTE _bState)
{
  switch (_bState)
    {
    case DISABLE:
      vMskAPURegBit (devc, _bChannel, 0x02, 3);
      break;

    case ENABLE:
      vSetAPURegBit (devc, _bChannel, 0x02, 3);
      break;
    }
}

static void
vSetAPUSubmixGroup (maestro_devc * devc, BYTE _bChannel, BYTE _bSubmixGroup)
{
  vWrAPUReg (devc, _bChannel, 0x02,
	     (WORD) ((wRdAPUReg (devc, _bChannel, 0x02) & 0xFFF8) |
		     _bSubmixGroup));
}

static void
vSetAPU6dB (maestro_devc * devc, BYTE _bChannel, BYTE _bState)
{
  switch (_bState)
    {
    case DISABLE:
      vMskAPURegBit (devc, _bChannel, 0x02, 4);
      break;

    case ENABLE:
      vSetAPURegBit (devc, _bChannel, 0x02, 4);
      break;
    }
}

static void
vSetAPUType (maestro_devc * devc, BYTE _bChannel, BYTE _bType)
{
  vWrAPUReg (devc, _bChannel, 0x00,
	     (wRdAPUReg (devc, _bChannel, 0x00) & 0xFF0F) | (_bType << 4));
}

static void
vSetAPUDMA (maestro_devc * devc, BYTE _bChannel, BYTE _bState)
{
  switch (_bState)
    {
    case DISABLE:
      vMskAPURegBit (devc, _bChannel, 0x00, 14);
      break;

    case ENABLE:
      vSetAPURegBit (devc, _bChannel, 0x00, 14);
      break;
    }
}

static void
vSetAPUFilterType (maestro_devc * devc, BYTE _bChannel, BYTE _bFilterType)
{
  vWrAPUReg (devc, _bChannel, 0x00,
	     (WORD) ((wRdAPUReg (devc, _bChannel, 0x00) & 0xFFF3) |
		     (_bFilterType << 2)));
}

static void
vSetAPUFilterQ (maestro_devc * devc, BYTE _bChannel, BYTE _bFilterQ)
{
  vWrAPUReg (devc, _bChannel, 0x00,
	     (WORD) ((wRdAPUReg (devc, _bChannel, 0x00) & 0xFFFC) |
		     _bFilterQ));
}

static void
vSetAPUFilterTuning (maestro_devc * devc, BYTE _bChannel, BYTE _bFilterTuning)
{
  vWrAPUReg (devc, _bChannel, 0x0A,
	     (WORD) ((wRdAPUReg (devc, _bChannel, 0x0A) & 0x00FF) |
		     ((WORD) _bFilterTuning << 8)));
}

static void
vSetAPUPolarPan (maestro_devc * devc, BYTE _bChannel, BYTE _bPolarPan)
{
  vWrAPUReg (devc, _bChannel, 0x0A,
	     (WORD) ((wRdAPUReg (devc, _bChannel, 0x0A) & 0xFFC0) |
		     _bPolarPan));
}

static void
vSetAPUDataSourceA (maestro_devc * devc, BYTE _bChannel, BYTE _bDataSourceA)
{
  vWrAPUReg (devc, _bChannel, 0x0B,
	     (WORD) ((wRdAPUReg (devc, _bChannel, 0x0B) & 0xFF80) |
		     _bDataSourceA));
}

static void
vSetAPUAmplitudeNow (maestro_devc * devc, BYTE _bChannel, BYTE _bAmplitude)
{
  vWrAPUReg (devc, _bChannel, 0x09,
	     (WORD) ((wRdAPUReg (devc, _bChannel, 0x09) & 0x00FF) |
		     (((WORD) _bAmplitude << 8))));
}

static void
vSetAPUWave64kPage (maestro_devc * devc, BYTE _bChannel, DWORD _wWaveStart)
{
  vWrAPUReg (devc, _bChannel, 0x04, ((_wWaveStart >> 16) & 0xFF) << 8);
}

static void
vSetAPUWaveStart (maestro_devc * devc, BYTE _bChannel, WORD _wWaveStart)
{
  vWrAPUReg (devc, _bChannel, 0x05, _wWaveStart);
}

static void
vSetAPUWaveEnd (maestro_devc * devc, BYTE _bChannel, WORD _wWaveEnd)
{
  vWrAPUReg (devc, _bChannel, 0x06, _wWaveEnd);
}

static void
vSetAPUWaveLoop (maestro_devc * devc, BYTE _bChannel, WORD _wWaveLoop)
{
  vWrAPUReg (devc, _bChannel, 0x07, _wWaveLoop);
}

static void
vSetAPUWavePtr (maestro_devc * devc, BYTE _bChannel, DWORD _dwWaveStart,
		WORD _wWaveLength)
{
  /* start of sample */
  vSetAPUWave64kPage (devc, _bChannel, _dwWaveStart);
  vSetAPUWaveStart (devc, _bChannel, LOWORD (_dwWaveStart));
  vSetAPUWaveEnd (devc, _bChannel, LOWORD ((_dwWaveStart + _wWaveLength)));
  vSetAPUWaveLoop (devc, _bChannel, _wWaveLength);
}

/*****************************************************************************/
/* Write Wave Cache Control I/O Port                                         */
/*                                                                           */
/*****************************************************************************/
static void
vWrWCControlReg (maestro_devc * devc, WORD _wWCControlRegFlags)
{
  OUTW (devc->osdev, _wWCControlRegFlags, WAVE_CACHE_CONTROL);
}

/*****************************************************************************/
/* Read Wave Cache Control I/O Port                                          */
/*                                                                           */
/*****************************************************************************/
static WORD
wRdWCControlReg (maestro_devc * devc)
{
  return ((WORD) (INW (devc->osdev, WAVE_CACHE_CONTROL)));
}

/*****************************************************************************/
/* Set Wave Cache Control Bit Flag                                           */
/*                                                                           */
/*****************************************************************************/
static void
vSetWCControlRegBit (maestro_devc * devc, BYTE _bWCControlRegBit)
{
  vWrWCControlReg (devc, WSETBIT (wRdWCControlReg (devc), _bWCControlRegBit));
}

/*****************************************************************************/
/* Mask Wave Cache Control Bit Flag                                          */
/*                                                                           */
/*****************************************************************************/
static void
vMskWCControlRegBit (maestro_devc * devc, BYTE _bWCControlRegBit)
{
  vWrWCControlReg (devc, WMSKBIT (wRdWCControlReg (devc), _bWCControlRegBit));
}

/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*****************************************************************************/
static void
vSetWCEnable (maestro_devc * devc, BYTE _bState)
{
  switch (_bState)
    {
    case DISABLE:
      vMskWCControlRegBit (devc, 8);
      break;

    case ENABLE:
      vSetWCControlRegBit (devc, 8);
      break;
    }
}

/*****************************************************************************/
/*                                                                           */
/*                                                                           */
/*****************************************************************************/
/*ARGSUSED*/
void
vSetTimer (maestro_devc * devc, WORD _wInterruptFreq)
{

  int prescale;
  int divide;

  /* XXX make freq selector much smarter, see calc_bob_rate */
  int freq = 200;

  /* compute ideal interrupt frequency for buffer size & play rate */
  /* first, find best prescaler value to match freq */
  for (prescale = 5; prescale < 12; prescale++)
    if (freq > (SYSCLK >> (prescale + 9)))
      break;

  /* next, back off prescaler whilst getting divider into optimum range */
  divide = 1;
  while ((prescale > 5) && (divide < 32))
    {
      prescale--;
      divide <<= 1;
    }
  divide >>= 1;

  /* now fine-tune the divider for best match */
  for (; divide < 31; divide++)
    if (freq >= ((SYSCLK >> (prescale + 9)) / (divide + 1)))
      break;

  /* divide = 0 is illegal, but don't let prescale = 4! */
  if (divide == 0)
    {
      divide++;
      if (prescale > 5)
	prescale--;
    }
  vWrIDR (devc, 0x06, (WORD) (0x9000 | (prescale << 5) | divide));
}

/*
 *****************************************************************************
 */

static int
maestro_audio_set_rate (int dev, int arg)
{
  maestro_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
maestro_audio_set_channels (int dev, short arg)
{
  maestro_portc *portc = audio_engines[dev]->portc;

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

  return portc->channels;
}

static unsigned int
maestro_audio_set_format (int dev, unsigned int arg)
{
  maestro_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
maestro_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static void maestro_audio_trigger (int dev, int state);

static void
maestro_audio_reset (int dev)
{
  maestro_audio_trigger (dev, 0);
}

static void
maestro_audio_reset_input (int dev)
{
  maestro_portc *portc = audio_engines[dev]->portc;
  maestro_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
maestro_audio_reset_output (int dev)
{
  maestro_portc *portc = audio_engines[dev]->portc;
  maestro_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
maestro_audio_open (int dev, int mode, int open_flags)
{
  oss_native_word flags;
  maestro_portc *portc = audio_engines[dev]->portc;
  maestro_devc *devc = audio_engines[dev]->devc;

  MUTEX_ENTER_IRQDISABLE (devc->lock, flags);
  if (portc->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->lock, flags);
      return -EBUSY;
    }
#if 1
  if ((mode & OPEN_READ))
    {
      audio_engines[dev]->flags |= ADEV_16BITONLY;
    }
  else
    {
      audio_engines[dev]->flags &= ~(ADEV_16BITONLY);
    }
#endif
  if (devc->open_mode & mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->lock, flags);
      return -EBUSY;
    }

  devc->open_mode |= mode;

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

  return 0;
}

static void
maestro_audio_close (int dev, int mode)
{
  maestro_portc *portc = audio_engines[dev]->portc;
  maestro_devc *devc = audio_engines[dev]->devc;

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

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

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

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

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

}

static void
maestro_audio_trigger (int dev, int state)
{
  oss_native_word flags;
  maestro_portc *portc = audio_engines[dev]->portc;
  maestro_devc *devc = audio_engines[dev]->devc;

  MUTEX_ENTER_IRQDISABLE (devc->lock, 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->bits == AFMT_U8)
		{
		  vSetAPUType (devc, bAPURPlay, 0x03);
		  if (portc->channels == 2)
		    {
		      vSetAPUType (devc, bAPURPlay, 0x04);
		      vSetAPUType (devc, bAPULPlay, 0x04);
		    }
		}
	      if (portc->bits == AFMT_S16_LE)
		{
		  vSetAPUType (devc, bAPURPlay, 0x01);
		  if (portc->channels == 2)
		    {
		      vSetAPUType (devc, bAPURPlay, 0x02);
		      vSetAPUType (devc, bAPULPlay, 0x02);
		    }
		}
	      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;
	      vSetAPUType (devc, bAPURPlay, 0x0000);
	      vSetAPUType (devc, bAPULPlay, 0x0000);
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      vSetAPUType (devc, bAPULMix, 0x09);
	      vSetAPUType (devc, bAPULSrc, 0x0B);
	      if (portc->channels == 2)
		{
		  vSetAPUType (devc, bAPURMix, 0x09);
		  vSetAPUType (devc, bAPURSrc, 0x0B);
		}
	      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;
	      vSetAPUType (devc, bAPURMix, 0x00);
	      vSetAPUType (devc, bAPURSrc, 0x00);
	      vSetAPUType (devc, bAPULMix, 0x00);
	      vSetAPUType (devc, bAPULSrc, 0x00);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->lock, flags);
}

/*ARGSUSED*/
static int
maestro_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  maestro_devc *devc = audio_engines[dev]->devc;
  maestro_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;

  oss_native_word flags;

  WORD wIndex, channel;
  unsigned int wSampleRate = portc->speed;
  int skip = 2;

#if 0
  if (portc->channels == 2)
    {
      cmn_err (CE_WARN, "Stereo recording not supported\n");
      return -EIO;
    }
#endif

  MUTEX_ENTER_IRQDISABLE (devc->lock, flags);

  devc->wApuBufferSize = dmap->bytes_in_use >> 1;

  if (wSampleRate > 47999)
    wSampleRate = 47999;
  if (wSampleRate < 5000)
    wSampleRate = 5000;


  if (portc->channels == 2)
    {
      devc->wApuBufferSize >>= 1;
      if (portc->bits == 16)
	wSampleRate <<= 1;
      skip = 1;
    }

  for (channel = 2; channel < 6; channel += skip)
    {
      int bsize, route;
      unsigned int physaddr;
      unsigned int rate;

      /* Clear out the APU */
      for (wIndex = 0; wIndex < 16; wIndex++)
	vWrAPUReg (devc, channel, (WORD) wIndex, 0x0000);

      /* data seems to flow from the codec, through an apu into
         the 'mixbuf' bit of page, then through the SRC apu
         and out to the real 'buffer'. */

      if (channel & 0x04)
	{
	  /* codec to mixbuf left */
	  if (!(channel & 0x01))
	    physaddr = devc->dma_mix.base;
	  else
	    /* codec to mixbuf right */
	    physaddr = devc->dma_mix.base + (PAGE_SIZE >> 4);


	  bsize = PAGE_SIZE >> 5;	/* 256 bytes needed for Mixbuf */
	  route = 0x14 + (channel - 4);	/* input routed from parallelin base */

	  /* The Mixer always runs at 48Khz. */
	  rate = 0x10000;
	  vSetAPUFrequency (devc, channel, rate);

	}
      else
	{
	  /* sample rate converter takes input from the mixer apu and 
	     outputs it to system memory */

	  if (!(channel & 0x01))
	    /* left channel records its half */
	    physaddr = dmap->dmabuf_phys;

	  else
	    /* right channel records its half */
	    physaddr = dmap->dmabuf_phys + devc->wApuBufferSize * 2;

	  bsize = devc->wApuBufferSize;
	  /* get input from inputing apu */
	  route = channel + 2;

	  /* SRC takes 48Khz data from mixer and converts it to requested rate */
	  rate = (DWORD) wCalculateAPUFrequency (wSampleRate);
	  vSetAPUFrequency (devc, channel, rate);


	}

      /* set the wavecache control reg */
      vSetWCChannelTagAddr (devc, channel,
			    (LOWORD (physaddr) - 0x10) & 0xFFF8);
      vSetWCChannelStereo (devc, channel, 0);
      vSetWCChannelWordSize (devc, channel, 1);

      physaddr -= devc->dmapages_phys;
      physaddr >>= 1;		/* words */

      /* base offset of dma calcs when reading the pointer
         on this left one */
#if 0
      if (channel == 2)
	devc->dma_adc.base = physaddr & 0xFFFF;
#endif
      physaddr |= 0x00400000;	/* bit 22 -> System RAM */

      /* Load the buffer into the wave engine */
      vSetAPUWavePtr (devc, channel, physaddr, bsize);

      /* Now set the rest of the APU params */
      vSetAPUSubmixMode (devc, channel, ENABLE);
      vSetAPUSubmixGroup (devc, channel, 0x0);
      vSetAPU6dB (devc, channel, DISABLE);
      vSetAPUAmplitudeNow (devc, channel, 0xF0);
      vSetAPUFilterTuning (devc, channel, 0x8F);
      vSetAPUPolarPan (devc, channel, 0x08);
      vSetAPUFilterType (devc, channel, 0x03);
      vSetAPUFilterQ (devc, channel, 0x03);
      vSetAPUDMA (devc, channel, ENABLE);

      /* route input */
      vSetAPUDataSourceA (devc, channel, route);
    }

  vMskIDRBit (devc, 0x11, 0);
  vMskIDRBit (devc, 0x17, 0);	/* Disable Bob Timer Interrupts */
  vSetTimer (devc, SYSCLK / (devc->wApuBufferSize));
  vSetIDRBit (devc, 0x11, 0);
  vSetIDRBit (devc, 0x17, 0);	/* Enable Bob Timer Interrupts */
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;

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

/*ARGSUSED*/
static int
maestro_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  maestro_devc *devc = audio_engines[dev]->devc;
  maestro_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;

  oss_native_word flags;

  WORD wIndex, i;
  DWORD dwPhysAddr;
  unsigned int wSampleRate = portc->speed;
  int numchans = 0;

  MUTEX_ENTER_IRQDISABLE (devc->lock, flags);

  devc->wApuBufferSize = dmap->bytes_in_use >> 1;

  if ((portc->bits == 8) && (portc->channels == 1))
    {
      wSampleRate >>= 1;
    }

  if (portc->channels == 2)
    {
      numchans++;
      if (portc->bits == 16)
	devc->wApuBufferSize >>= 1;
    }

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

      dwPhysAddr = dmap->dmabuf_phys;

      vSetWCChannelTagAddr (devc, i,
			    (WORD) ((LOWORD (dwPhysAddr) - 0x10) & 0xFFF8));

      if (portc->bits == 16)
	vSetWCChannelWordSize (devc, i, 1);
      else
	vSetWCChannelWordSize (devc, i, 0);

      if (portc->channels == 2)
	vSetWCChannelStereo (devc, i, 1);
      else
	vSetWCChannelStereo (devc, i, 0);

      /* Calculate WP base address */
      dwPhysAddr -= devc->dmapages_phys;
      dwPhysAddr >>= 1;		/* adjust for word size */
#if 0
      if (i)
	devc->dma_dac.base = dwPhysAddr & 0xFFFF;
#endif
      dwPhysAddr |= 0x00400000L;	/* Enable bit 22 for system ram access */


      if (portc->channels == 2)
	{
	  if (!i)
	    dwPhysAddr |= 0x00800000;
	  if (portc->bits == 16)
	    {
	      dwPhysAddr >>= 1;
	    }
	}
      for (wIndex = 0; wIndex < 16; wIndex++)
	vWrAPUReg (devc, i, (BYTE) wIndex, 0x0000);

      vSetAPUFrequency (devc, i,
			(DWORD) wCalculateAPUFrequency (wSampleRate));
      vSetAPUWavePtr (devc, i, dwPhysAddr, devc->wApuBufferSize);
      vSetAPU6dB (devc, i, DISABLE);
      vSetAPUAmplitudeNow (devc, i, 0xF0);
      vSetAPUFilterTuning (devc, i, 0x8F);
      if (portc->channels == 2)
	vSetAPUPolarPan (devc, i, (i ? 0x10 : 0x00));
      else
	vSetAPUPolarPan (devc, i, 0x08);
      vSetAPUFilterType (devc, i, 0x03);
      vSetAPUFilterQ (devc, i, 0x03);
      vSetAPUDMA (devc, i, ENABLE);
    }

  vMskIDRBit (devc, 0x11, 0);
  vMskIDRBit (devc, 0x17, 0);	/* Disable Bob Timer Interrupts */
  vSetTimer (devc, SYSCLK / devc->wApuBufferSize);
  vSetIDRBit (devc, 0x11, 0);
  vSetIDRBit (devc, 0x17, 0);	/* Enable Bob Timer Interrupts */
  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->lock, flags);
  return 0;
}

static int
maestro_alloc_buffer (int dev, dmap_t * dmap, int direction)
{
  maestro_devc *devc = audio_engines[dev]->devc;

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

  if (direction == PCM_ENABLE_OUTPUT)
    {
      dmap->dmabuf = devc->dma_dac.rawbuf;
      dmap->dmabuf_phys = devc->dma_dac.base;
      dmap->buffsize = devc->dma_dac.dmasize;
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      dmap->dmabuf = devc->dma_adc.rawbuf;
      dmap->dmabuf_phys = devc->dma_adc.base;
      dmap->buffsize = devc->dma_adc.dmasize;
    }
  return 0;
}

/*ARGSUSED*/
static int
maestro_free_buffer (int dev, dmap_t * dmap, int direction)
{
  if (dmap->dmabuf == NULL)
    return 0;

  dmap->dmabuf = NULL;
  dmap->dmabuf_phys = 0;
  dmap->buffsize = 0;

  return 0;
}

/*ARGSUSED*/
static int
maestro_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  maestro_devc *devc = audio_engines[dev]->devc;
  maestro_portc *portc = audio_engines[dev]->portc;
  int ptr = 0;
  oss_native_word flags;

  if (!(portc->open_mode & direction))
    return 0;

  MUTEX_ENTER_IRQDISABLE (devc->low_lock, flags);
  if (direction == PCM_ENABLE_INPUT)
    {
      ptr = wRdAPUReg (devc, bAPULSrc, 0x05);
      ptr = (ptr - devc->dma_adc.base) & 0xFFFE;
    }

  if (direction == PCM_ENABLE_OUTPUT)
    {
      ptr = wRdAPUReg (devc, bAPURPlay, 0x05);
      ptr = (ptr - devc->dma_dac.base) & 0xFFFE;
    }
  ptr = (ptr % devc->wApuBufferSize) << 1;
  MUTEX_EXIT_IRQRESTORE (devc->low_lock, flags);
  return (ptr);
}

static void
set_maestro_base (maestro_devc * devc)
{
  unsigned long packed_phys = devc->dmapages_phys >> 12;
  vWrWCReg (devc, 0x01FC, packed_phys);
  vWrWCReg (devc, 0x01FD, packed_phys);
  vWrWCReg (devc, 0x01FE, packed_phys);
  vWrWCReg (devc, 0x01FF, packed_phys);
}

static int
allocate_maestro_bufs (maestro_devc * devc)
{
  unsigned char *rawbuf = NULL;
  int size, extra, start;
  oss_native_word phaddr;

  /* size = size of rec/play buffers 
   * start = offset from beginning for rec/play buffers
   * extra=size of mix buf + start
   */
#if defined(sun) || defined(linux)
  size = 32 * 1024;
  start = 16 * 1024;
  extra = 16 * 1024;
#else
#if defined (__FreeBSD__)
  size = 64 * 1024;
  start = 8 * 1024;
  extra = 16 * 1024;
#else
  size = 96 * 1024;
  start = 16 * 1024;
  extra = 16 * 1024;
#endif
#endif
  devc->dmalen = size + extra;

  rawbuf =
    (void *) CONTIG_MALLOC (devc->osdev, devc->dmalen, MEMLIMIT_28BITS,
			    &phaddr);

  if (!rawbuf)
    return 1;

  DDB (cmn_err (CE_WARN, "addr=%p, size=%d\n", (void *) rawbuf, size));

  if ((phaddr + size - 1) & ~((1 << 28) - 1))
    {
      cmn_err (CE_WARN, "DMA buffer beyond 256MB\n");
      CONTIG_FREE (devc->osdev, rawbuf, size + extra);
      return 1;
    }

  devc->dmapages = rawbuf;
  devc->dmapages_phys = phaddr;

  devc->dma_dac.rawbuf = rawbuf + start;
  devc->dma_dac.dmasize = size / 2;
  devc->dma_dac.base = phaddr + start;

  devc->dma_adc.rawbuf = devc->dma_dac.rawbuf + size / 2;
  devc->dma_adc.dmasize = size / 2;
  devc->dma_adc.base = devc->dma_dac.base + size / 2;

  devc->dma_mix.rawbuf = rawbuf + 4096;
  devc->dma_mix.base = phaddr + 4096;
  devc->dma_mix.dmasize = 4096;
#ifdef linux
  /* reserve the pages for MMAP */
  oss_reserve_pages ((oss_native_word) devc->dma_dac.rawbuf,
		     (oss_native_word) devc->dma_dac.rawbuf +
		     devc->dma_dac.dmasize - 1);
#endif
  return 0;
}

static audiodrv_t maestro_audio_driver = {
  maestro_audio_open,
  maestro_audio_close,
  maestro_audio_output_block,
  maestro_audio_start_input,
  maestro_audio_ioctl,
  maestro_audio_prepare_for_input,
  maestro_audio_prepare_for_output,
  maestro_audio_reset,
  NULL,
  NULL,
  maestro_audio_reset_input,
  maestro_audio_reset_output,
  maestro_audio_trigger,
  maestro_audio_set_rate,
  maestro_audio_set_format,
  maestro_audio_set_channels,
  NULL,
  NULL,
  NULL,				/* maestro_check_input, */
  NULL,				/* maestro_check_output, */
  maestro_alloc_buffer,
  maestro_free_buffer,
  NULL,
  NULL,
  maestro_get_buffer_pointer
};

static mixer_driver_t pt101_mixer_driver = {
  pt101_mixer_ioctl
};


static void
maestro_ac97_reset (maestro_devc * devc)
{
  int save_68;

/* Reset the Codec */
  OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x38) & 0xfffc,
	devc->base + 0x38);
  OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x3a) & 0xfffc,
	devc->base + 0x3a);
  OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x3c) & 0xfffc,
	devc->base + 0x3c);
  /* reset the first codec */
  OUTW (devc->osdev, 0x0000, devc->base + 0x36);
  save_68 = INW (devc->osdev, devc->base + 0x68);
  if (devc->gpio & 0x1)
    save_68 |= 0x10;
  OUTW (devc->osdev, 0xfffe, devc->base + 0x64);	/* tickly gpio 0.. */
  OUTW (devc->osdev, 0x0001, devc->base + 0x68);
  OUTW (devc->osdev, 0x0000, devc->base + 0x60);
  oss_udelay (10);
  OUTW (devc->osdev, 0x0001, devc->base + 0x60);
  oss_udelay (100);
  OUTW (devc->osdev, save_68 | 0x1, devc->base + 0x68);	/* now restore .. */
  OUTW (devc->osdev, (INW (devc->osdev, devc->base + 0x38) & 0xfffc) | 0x1,
	devc->base + 0x38);
  OUTW (devc->osdev, (INW (devc->osdev, devc->base + 0x3a) & 0xfffc) | 0x1,
	devc->base + 0x3a);
  OUTW (devc->osdev, (INW (devc->osdev, devc->base + 0x3c) & 0xfffc) | 0x1,
	devc->base + 0x3c);

  /* now the second codec */
  OUTW (devc->osdev, 0x0000, devc->base + 0x36);
  OUTW (devc->osdev, 0xfff7, devc->base + 0x64);
  save_68 = INW (devc->osdev, devc->base + 0x68);
  OUTW (devc->osdev, 0x0009, devc->base + 0x68);
  OUTW (devc->osdev, 0x0001, devc->base + 0x60);
  oss_udelay (10);
  OUTW (devc->osdev, 0x0009, devc->base + 0x60);
  oss_udelay (100);
  OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x38) & 0xfffc,
	devc->base + 0x38);
  OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x3a) & 0xfffc,
	devc->base + 0x3a);
  OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x3c) & 0xfffc,
	devc->base + 0x3c);
}

static int
init_maestro (maestro_devc * devc)
{
  int my_mixer;
  int i;
  oss_native_word n;
  unsigned short w;
  int wIndex;
  int first_dev = 0;
  int codec_id;
  int bRow, bAPU;

/* Sound reset */
  OUTW (devc->osdev, 0x2000, 0x18 + devc->base);
  oss_udelay (10);
  OUTW (devc->osdev, 0x0000, 0x18 + devc->base);
  oss_udelay (10);

/* Setup 0x34 and 0x36 regs */
  OUTW (devc->osdev, 0xc090, devc->base + 0x34);
  oss_udelay (1000);
  OUTW (devc->osdev, 0x3000, devc->base + 0x36);
  oss_udelay (1000);

/* reset ac97 link */
  maestro_ac97_reset (devc);

/* DirectSound*/
  n = INL (devc->osdev, devc->base + 0x34);
  n &= ~0xF000;
  n |= 12 << 12;		/* Direct Sound, Stereo */
  OUTL (devc->osdev, n, devc->base + 0x34);

  n = INL (devc->osdev, devc->base + 0x34);
  n &= ~0x0F00;			/* Modem off */
  OUTL (devc->osdev, n, devc->base + 0x34);

  n = INL (devc->osdev, devc->base + 0x34);
  n &= ~0x00F0;
  n |= 9 << 4;			/* DAC, Stereo */
  OUTL (devc->osdev, n, devc->base + 0x34);

  n = INL (devc->osdev, devc->base + 0x34);
  n &= ~0x000F;			/* ASSP off */
  OUTL (devc->osdev, n, devc->base + 0x34);

  n = INL (devc->osdev, devc->base + 0x34);
  n |= (1 << 29);		/* Enable ring bus */
  OUTL (devc->osdev, n, devc->base + 0x34);

  n = INL (devc->osdev, devc->base + 0x34);
  n |= (1 << 28);		/* Enable serial bus */
  OUTL (devc->osdev, n, devc->base + 0x34);

  n = INL (devc->osdev, devc->base + 0x34);
  n &= ~0x00F00000;		/* MIC off */
  OUTL (devc->osdev, n, devc->base + 0x34);

  n = INL (devc->osdev, devc->base + 0x34);
  n &= ~0x000F0000;		/* I2S off */
  OUTL (devc->osdev, n, devc->base + 0x34);

  w = INW (devc->osdev, devc->base + 0x18);
  w &= ~(1 << 7);		/* ClkRun off */
  OUTW (devc->osdev, w, devc->base + 0x18);

  w = INW (devc->osdev, devc->base + 0x18);
  w &= ~(1 << 6);		/* Harpo off */
  OUTW (devc->osdev, w, devc->base + 0x18);

  w = INW (devc->osdev, devc->base + 0x18);
  w &= ~(1 << 4);		/* ASSP irq off */
  OUTW (devc->osdev, w, devc->base + 0x18);

  w = INW (devc->osdev, devc->base + 0x18);
  w &= ~(1 << 3);		/* ISDN irq off */
  OUTW (devc->osdev, w, devc->base + 0x18);

  w = INW (devc->osdev, devc->base + 0x18);
  w |= (1 << 2);		/* Direct Sound IRQ on */
  OUTW (devc->osdev, w, devc->base + 0x18);

  w = INW (devc->osdev, devc->base + 0x18);
  w &= ~(1 << 1);		/* MPU401 IRQ off */
  OUTW (devc->osdev, w, devc->base + 0x18);

  w = INW (devc->osdev, devc->base + 0x18);
  w &= ~(1 << 0);		/* SB IRQ on */
  OUTW (devc->osdev, w, devc->base + 0x18);

  OUTB (devc->osdev, 0x00, gwPTBaseIO + 0xA4);
  OUTB (devc->osdev, 0x03, gwPTBaseIO + 0xA2);
  OUTB (devc->osdev, 0x00, gwPTBaseIO + 0xA6);

  if (devc->model == ESS_MAESTRO)
    {
/* Clear out wavecache */
      for (wIndex = 0; wIndex < 0x0200; wIndex++)
	vWrWCReg (devc, wIndex, 0x0000);
/* Clear out APU */
      for (wIndex = 0; wIndex < 0x40; wIndex++)
	vWrAPUReg (devc, wIndex, 0x00, 0x0000);
/* Clear out WP */
      for (wIndex = 0; wIndex < 0x1F; wIndex++)
	devc->wIDRRegDataTable[wIndex] = 0x0000;
    }
  else
    {
/* Clear out Wave Cache */
      for (bRow = 0; bRow < 0x10; bRow++)
	{
	  vWrWCReg (devc, (WORD) (0x01E0 + bRow), 0x0000);
	}

      /* Clear Control RAM */
      for (bAPU = 0x00; bAPU < 0x40; bAPU++)
	{
	  for (bRow = 0; bRow < 0x0E; bRow++)
	    {
	      vWrAPUReg (devc, bAPU, bRow, 0x0000);
	    }
	}
    }

  vWrIDR (devc, 0x02, 0x0000);	/* CRam Increment */
  vWrIDR (devc, 0x08, 0xB004);	/* Audio Serial Configuration */
  vWrIDR (devc, 0x09, 0x001B);	/* Audio Serial Configuration */
  vWrIDR (devc, 0x0A, 0x8000);	/* Audio Serial Configuration */
  vWrIDR (devc, 0x0B, 0x3F37);	/* Audio Serial Configuration */
  vWrIDR (devc, 0x0C, 0x0098);

  /* parallel out */
  vWrIDR (devc, 0x0C, (wRdIDR (devc, 0x0C) & ~0xF000) | 0x8000);
  /* parallel in */
  vWrIDR (devc, 0x0C, (wRdIDR (devc, 0x0C) & ~0x0F00) | 0x0500);

  vWrIDR (devc, 0x0D, 0x7632);	/*Audio Serial Configuration */
  OUTW (devc->osdev, INW (devc->osdev, 0x14 + devc->base) | (1 << 8),
	0x14 + devc->base);
  OUTW (devc->osdev, INW (devc->osdev, 0x14 + devc->base) & 0xFE03,
	0x14 + devc->base);
  OUTW (devc->osdev, (INW (devc->osdev, 0x14 + devc->base) & 0xFFFC),
	0x14 + devc->base);
  OUTW (devc->osdev, INW (devc->osdev, 0x14 + devc->base) | (1 << 7),
	0x14 + devc->base);

  /* enable the Wavecache */
  vSetWCEnable (devc, ENABLE);
  /* Unmask WP interrupts */
  OUTW (devc->osdev, (WORD) (INW (devc->osdev, devc->base + 0x18)
			     | 0x0004), devc->base + 0x18);

  if (devc->model == ESS_MAESTRO)
    {
      OUTW (devc->osdev, 0xA1A0, gwPTBaseIO + 0x14);
    }
  else
    {
      OUTW (devc->osdev, 0xA1A0, gwPTBaseIO + 0x14);
    }

/* First get the Codec ID type - check if it is PT101 or AC97*/
  codec_id = ac97_read (devc, 0x00);

/* initialize the PT101 Codec */
  if (codec_id == 0x80)
    {
      ac97_write (devc, 0x09, 0x0000);	/*DAC Mute */
      ac97_write (devc, 0x0F, 0x0000);
      ac97_write (devc, 0x11, 0x0000);
      ac97_write (devc, 0x14, 0x0000);
      ac97_write (devc, 0x1A, 0x0105);
      ac97_write (devc, 0x1E, 0x0101);	/*set PT101 reg 0x1F */
      ac97_write (devc, 0x1F, 0x8080);	/*set PT101 reg 0x1F */
    }

  if ((devc->model == ESS_MAESTRO2E) || (devc->model == ESS_MAESTRO2))
    {
      outpw (gwPTBaseIO + 0x02, (WORD) 0x0001);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x03C0);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x1);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x1);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x3c0);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x0000);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x4010);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x0001);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x03D0);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x1);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x1);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x3d0);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x0000);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x4010);
    }
  else
    {
      outpw (gwPTBaseIO + 0x02, (WORD) 0x0001);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x03C0);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x00);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x401F);

      outpw (gwPTBaseIO + 0x02, (WORD) 0x0001);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x03D0);
      outpw (gwPTBaseIO + 0x02, (WORD) 0x00);
      outpw (gwPTBaseIO + 0x00, (WORD) 0x401F);
    }

/***********************ALLOCATE MEMORY************************/
  {
    int ret;

    ret = allocate_maestro_bufs (devc);
    if (ret != 0)
      {
	cmn_err (CE_WARN, "Couldn't allocate Maestro memory\n");
	return 1;
      }
    set_maestro_base (devc);
  }
/******************INIT MIXERS*********************************/
  if (codec_id == 0x80)
    {
      my_mixer = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
				    devc->osdev,
				    devc->osdev,
				    "ESS PT101",
				    &pt101_mixer_driver,
				    sizeof (mixer_driver_t), devc);
      if (my_mixer < 0)
	return 0;
      else
	pt101_mixer_reset (devc);
    }
  else
    {
      /* Reset the codec */
      my_mixer = ac97_install (&devc->ac97devc, "Maestro2 AC97 Mixer",
			       ac97_read, ac97_write, devc, devc->osdev);
      if (my_mixer < 0)
	return 0;
    }
  for (i = 0; i < MAX_PORTC; i++)
    {
      int adev;
      int caps = ADEV_AUTOMODE;
      maestro_portc *portc = &devc->portc[i];
      char tmp_name[100];
      strcpy (tmp_name, devc->chip_name);

      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,
					&maestro_audio_driver,
					sizeof (audiodrv_t),
					caps,
					AFMT_U8 | AFMT_S16_LE, 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;
#if 0
	  audio_engines[adev]->min_block = 4096;
	  audio_engines[adev]->max_block = 4096;
#endif
	  audio_engines[adev]->min_rate = 5000;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  portc->audiodev = adev;
	  portc->open_mode = 0;
	  portc->trigger_bits = 0;
	  portc->speed = 0;
	  portc->bits = 0;
	  portc->channels = 0;
	  audio_engines[adev]->mixer_dev = my_mixer;
	  audio_engines[adev]->vmix_flags = VMIX_MULTIFRAG;
	}
    }
  return 1;
}

/* NEC Versas ? */
#define NEC_VERSA_SUBID1        0x80581033
#define NEC_VERSA_SUBID2        0x803c1033

int
maestro_attach (oss_device_t * osdev)
{
  unsigned short sdata;
  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr, subid;
  maestro_devc *devc;

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

  pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
  pci_read_config_dword (osdev, PCI_SUBSYSTEM_VENDOR_ID, &subid);
  pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision);
  pci_read_config_word (osdev, PCI_COMMAND, &pci_command);
  pci_read_config_word (osdev, PCI_DEVICE_ID, &device);
  pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line);
  pci_read_config_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr);
  if ((vendor != ESS_VENDOR_ID && vendor != ESS2_VENDOR_ID) ||
      (device != ESS_MAESTRO && device != ESS_MAESTRO2 &&
       device != ESS_MAESTRO2E))

    return 0;

  DDB (cmn_err (CE_WARN, "Maestro I/O base %04x\n", pci_ioaddr));


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

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

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


  devc->osdev = osdev;
  osdev->devc = devc;

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

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

  switch (device)
    {
    case ESS_MAESTRO2E:
      devc->model = MD_MAESTRO2E;
      devc->chip_name = "Maestro-2E";
      SYSCLK = 50000000L;
      break;

    case ESS_MAESTRO2:
      devc->model = MD_MAESTRO2;
      devc->chip_name = "Maestro-2";
      SYSCLK = 50000000L;
      break;

    case ESS_MAESTRO:
    default:
      devc->model = MD_MAESTRO1;
      devc->chip_name = "Maestro-1";
      SYSCLK = 49152000L;
      break;
    }

  if (subid == NEC_VERSA_SUBID1 || subid == NEC_VERSA_SUBID2)
    {
      /* turn on external amp? */
      OUTW (devc->osdev, 0xf9ff, devc->base + 0x64);
      OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x68) | 0x600,
	    devc->base + 0x68);
      OUTW (devc->osdev, 0x0209, devc->base + 0x60);
    }


  /* Legacy Audio Control */
  pci_read_config_word (osdev, 0x40, &sdata);
  sdata |= (1 << 15);		/* legacy decode off */
  sdata &= ~(1 << 14);		/* Disable SIRQ */
  sdata &= ~(0x1f);		/* disable mpu irq/io, game port, fm, SB */

  pci_write_config_word (osdev, 0x40, sdata);

  /* MIDI/SB I/O, IRQ Control */
  pci_read_config_word (osdev, 0x50, &sdata);
  sdata &= ~(1 << 5);
  pci_write_config_word (osdev, 0x50, sdata);

  /* GPIO Control */
  pci_read_config_word (osdev, 0x52, &sdata);
  sdata &= ~(1 << 15);		/* Turn off internal clock multiplier */
  /* XXX how do we know which to use? */
  sdata &= ~(1 << 14);		/* External clock */
  sdata &= ~(1 << 7);		/* HWV off */
  sdata &= ~(1 << 6);		/* Debounce off */
  sdata &= ~(1 << 5);		/* GPIO 4:5 */
  sdata |= (1 << 4);		/* Disconnect from the CHI. */
  /* Enabling this made dell 7500 work. */
  sdata &= ~(1 << 2);		/* MIDI fix off (undoc) */
  sdata &= ~(1 << 1);		/* reserved, always write 0 */
  pci_write_config_word (osdev, 0x52, sdata);
  if (devc->model == ESS_MAESTRO2E)
    {
      pci_write_config_word (osdev, 0x54, 0x0000);
      pci_write_config_word (osdev, 0x56, 0x0000);
      pci_write_config_word (osdev, 0x58, 0x0000);
      pci_write_config_word (osdev, 0xC4, 0x8000);
      pci_read_config_word (osdev, 0x68, &devc->gpio);
    }

  MUTEX_INIT (devc->osdev, devc->lock, MH_DRV);
  MUTEX_INIT (devc->osdev, devc->low_lock, MH_DRV + 1);

  oss_register_device (osdev, devc->chip_name);

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

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


int
maestro_detach (oss_device_t * osdev)
{
  maestro_devc *devc = (maestro_devc *) osdev->devc;

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

  /* Stop WP interrutps */
  OUTW (devc->osdev, (WORD) (INW (devc->osdev, devc->base + 0x18) & 0xFFF8),
	devc->base + 0x18);
  OUTW (devc->osdev, 0x0001, devc->base + 0x04);
  vSetWCEnable (devc, DISABLE);
  vMskIDRBit (devc, 0x17, 0);	/* Disable Bob Timer Interrupts */

  oss_unregister_interrupts (devc->osdev);
  if (devc->dmapages != NULL)
    CONTIG_FREE (devc->osdev, devc->dmapages, devc->dmalen);

  MUTEX_CLEANUP (devc->lock);
  MUTEX_CLEANUP (devc->low_lock);

  oss_unregister_device (osdev);
  return 1;

}
