/*
 * Purpose: Driver for CMEDIA CM8738 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 "cmpci_cfg.h"
#include "oss_pci.h"
#include "uart401.h"

#define CMEDIA_VENDOR_ID	0x13F6
#define CMEDIA_CM8738		0x0111
#define CMEDIA_CM8338A		0x0100
#define CMEDIA_CM8338B		0x0101
/*
 * CM8338 registers definition
 */

#define FUNCTRL0      (devc->base+0x00)
#define FUNCTRL1      (devc->base+0x04)
#define CHFORMAT      (devc->base+0x08)
#define INT_HLDCLR    (devc->base+0x0C)
#define INT_STATUS    (devc->base+0x10)
#define LEGACY_CTRL   (devc->base+0x14)
#define MISC_CTRL     (devc->base+0x18)
#define TDMA_POS      (devc->base+0x1C)
#define MIXER         (devc->base+0x20)
#define MIXER_DATA    (devc->base+0x22)
#define MIXER_ADDR    (devc->base+0x23)
#define MIXER1        (devc->base+0x24)
#define MIXER2        (devc->base+0x25)
#define AUX_VOL       (devc->base+0x26)
#define MIXER3        (devc->base+0x27)
#define AC97          (devc->base+0x28)

#define CH0_FRAME1    (devc->base+0x80)
#define CH0_FRAME2    (devc->base+0x84)
#define CH1_FRAME1    (devc->base+0x88)
#define CH1_FRAME2    (devc->base+0x8C)

#define SPDIF_STAT    (devc->base+0x90)
#define MISC2_CTRL    (devc->base+0x92)

#define MPU_MIRROR    (devc->base+0x40)
#define FM_MIRROR     (devc->base+0x50)
#define JOY_MIRROR    (devc->base+0x60)

#define DSP_MIX_DATARESETIDX    (0x00)
#define DSP_MIX_OUTMIXIDX	(0x3c)
#define CM_CH0_ENABLE     0x01
#define CM_CH1_ENABLE     0x02
#define CM_CH0_RESET      0x04
#define CM_CH1_RESET      0x08
#define CM_CH0_RECORD	  0x01
#define CM_CH1_RECORD	  0x02
#define CM_CH0_PLAY	  ~0x01
#define CM_CH1_PLAY	  ~0x02
#define CM_CH0_INT        1
#define CM_CH1_INT        2
#define CM_EXTENT_CODEC   0x100
#define CM_EXTENT_MIDI    0x2
#define CM_EXTENT_SYNTH   0x4
#define CM_CFMT_STEREO    0x01
#define CM_CFMT_16BIT     0x02
#define CM_CFMT_MASK      0x03
#define CM_CFMT_DACSHIFT  0
#define CM_CFMT_ADCSHIFT  2

#define MUTE_LINE	1
#define MUTE_CD		2
#define MUTE_MIC	3
#define MUTE_AUX	4
#define REAR2LINE	5
#define CEN2LINE	6
#define BASS2LINE	7
#define CEN2MIC		8
#define MODE_4SPK	9
#define DUALDAC		10
#define MICBOOST	11
#define SPDIF_PLAY	12
#define SPDIF_LOOP	13
#define SPDIF_REC	14
#define SPDIF_IMON	15
#define SPDIF_POL	16
#define SPDIF_COPY	17
#define SPDIF_OPT	18
#define SPDIF_AC3	19
#define AUX_REC		20
#define AUX_LEVEL	21

#define CHAN0		   0x1
#define CHAN1		   0x2

static struct
{
  unsigned int rate;
  unsigned int lower;
  unsigned int upper;
  unsigned char freq;
}
rate_lookup[] =
{
  {
  5512, (0 + 5512) / 2, (5512 + 8000) / 2, 0}
  ,
  {
  8000, (5512 + 8000) / 2, (8000 + 11025) / 2, 4}
  ,
  {
  11025, (8000 + 11025) / 2, (11025 + 16000) / 2, 1}
  ,
  {
  16000, (11025 + 16000) / 2, (16000 + 22050) / 2, 5}
  ,
  {
  22050, (16000 + 22050) / 2, (22050 + 32000) / 2, 2}
  ,
  {
  32000, (22050 + 32000) / 2, (32000 + 44100) / 2, 6}
  ,
  {
  44100, (32000 + 44100) / 2, (44100 + 48000) / 2, 3}
  ,
  {
  48000, (48000 + 44100) / 2, (48000 + 96000) / 2, 7}
};

static unsigned char cmpci_recmasks_L[SOUND_MIXER_NRDEVICES] = {
  0x00,				/* SOUND_MIXER_VOLUME   */
  0x00,				/* SOUND_MIXER_BASS     */
  0x00,				/* SOUND_MIXER_TREBLE   */
  0x40,				/* SOUND_MIXER_SYNTH    */
  0x00,				/* SOUND_MIXER_PCM      */
  0x00,				/* SOUND_MIXER_SPEAKER  */
  0x10,				/* SOUND_MIXER_LINE     */
  0x01,				/* SOUND_MIXER_MIC      */
  0x04,				/* SOUND_MIXER_CD       */
  0x00,				/* SOUND_MIXER_IMIX     */
  0x00,				/* SOUND_MIXER_LINE1    */
  0x00,				/* SOUND_MIXER_RECLEV   */
  0x00,				/* SOUND_MIXER_IGAIN    */
  0x00				/* SOUND_MIXER_OGAIN    */
};

static unsigned char cmpci_recmasks_R[SOUND_MIXER_NRDEVICES] = {
  0x00,				/* SOUND_MIXER_VOLUME   */
  0x00,				/* SOUND_MIXER_BASS     */
  0x00,				/* SOUND_MIXER_TREBLE   */
  0x20,				/* SOUND_MIXER_SYNTH    */
  0x00,				/* SOUND_MIXER_PCM      */
  0x00,				/* SOUND_MIXER_SPEAKER  */
  0x08,				/* SOUND_MIXER_LINE     */
  0x01,				/* SOUND_MIXER_MIC      */
  0x02,				/* SOUND_MIXER_CD       */
  0x00,				/* SOUND_MIXER_IMIX     */
  0x00,				/* SOUND_MIXER_LINE1    */
  0x00,				/* SOUND_MIXER_RECLEV   */
  0x00,				/* SOUND_MIXER_IGAIN    */
  0x00				/* SOUND_MIXER_OGAIN    */
};

#define CMEDIA_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_LINE | \
				  SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_LINE1)

#define CMEDIA_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | \
			     SOUND_MASK_LINE | SOUND_MASK_MIC | \
			     SOUND_MASK_IGAIN | SOUND_MASK_CD | \
			     SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER |\
			     SOUND_MASK_LINE1|SOUND_MASK_RECLEV)

#define LEFT_CHN        0
#define RIGHT_CHN       1
/*
 * Mixer registers of CMPCI
 */
#define CMPCI_IMASK_L    0x3d
#define CMPCI_IMASK_R    0x3e


int default_levels[32] = {
  0x5a5a,			/* Master Volume */
  0x4b4b,			/* Bass */
  0x4b4b,			/* Treble */
  0x4b4b,			/* FM */
  0x4b4b,			/* PCM */
  0x4b4b,			/* PC Speaker */
  0x4b4b,			/* Ext Line */
  0x2020,			/* Mic */
  0x4b4b,			/* CD */
  0x0000,			/* Recording monitor */
  0x4b4b,			/* SB PCM */
  0x4b4b,			/* Recording level */
  0x4b4b,			/* Input gain */
  0x4b4b,			/* Output gain */
  0x4040,			/* Line1 */
  0x4040,			/* Line2 */
  0x1515			/* Line3 */
};

typedef struct cmpci_portc
{
  int speed, bits, channels;
  int open_mode;
  int trigger_bits;
  int audio_enabled;
  int audiodev;
  int dacfmt, adcfmt;
  int chan0_play, chan0_rec;
  int chan1_play, chan1_rec;
}
cmpci_portc;

#define MAX_PORTC 2

typedef struct cmpci_devc
{
  oss_device_t *osdev;
  oss_native_word base;
  int fm_attached;
  int irq;
  int max_channels;
  volatile unsigned char intr_mask;
  int model;
#define MDL_CM8738		1
#define MDL_CM8338A		2
#define MDL_CM8338B		3
#define MDL_CM8768		4
  char *chip_name;
  int chiprev;
  int mode_4spk;
  int dev_mode;
#define DEFAULT_MODE		1
#define DUALDAC_MODE		2
#define SPDIFIN_MODE		4
  unsigned char spdif_control_bits[24];
  /* Audio parameters */
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;
  int open_mode;
  int audio_initialized;
  cmpci_portc portc[MAX_PORTC];

  /* spdif/ac3 stuff */
  int spdif_enabled;
  int can_ac3;

  /* Mixer parameters */
  int mixer_dev;
  int *levels;
  int recmask;

  /* uart401 */
  int uart401_attached;
  uart401_devc uart401devc;
}
cmpci_devc;


static void
set_spdif_rate (cmpci_devc * devc, cmpci_portc * portc)
{
  if (portc->speed == 48000)
    {
      /* setup MISC control reg */
      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 24) | (1 << 15),
	    MISC_CTRL);
    }
  else
    {
      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 15),
	    MISC_CTRL);
      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 24),
	    MISC_CTRL);
    }
}

static void
setup_ac3 (cmpci_devc * devc, cmpci_portc * portc, int value)
{
  if (value && (portc->speed == 48000 || portc->speed == 44100))
    {
      if (devc->chiprev == 37)
	{
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) | (1 << 20),
		CHFORMAT);
	}
      else
	{
	  /* Enable AC3 */
	  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 18),
		MISC_CTRL);
	}
    }
  else
    {
      if (devc->chiprev == 37)
	{
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(1 << 20),
		CHFORMAT);
	}
      else
	{
	  /* Disable AC3 */
	  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 18),
		MISC_CTRL);
	}
    }
}


static void
cmpci_setmixer (cmpci_devc * devc, unsigned int port, unsigned int value)
{
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTB (devc->osdev, (unsigned char) (port & 0xff), MIXER_ADDR);
  oss_udelay (20);
  OUTB (devc->osdev, (unsigned char) (value & 0xff), MIXER_DATA);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  oss_udelay (20);
}

static unsigned int
cmpci_getmixer (cmpci_devc * devc, unsigned int port)
{
  unsigned int val;

  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTB (devc->osdev, (unsigned char) (port & 0xff), MIXER_ADDR);


  oss_udelay (20);
  val = INB (devc->osdev, MIXER_DATA);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  oss_udelay (20);
  return val;
}

struct mixer_def
{
  unsigned int regno:8;
  unsigned int bitoffs:4;
  unsigned int nbits:4;
};

typedef struct mixer_def mixer_tab[32][2];
typedef struct mixer_def mixer_ent;

#define MIX_ENT(name, reg_l, bit_l, len_l, reg_r, bit_r, len_r) \
        {{reg_l, bit_l, len_l}, {reg_r, bit_r, len_r}}

static mixer_tab cmpci_mix = {
  MIX_ENT (SOUND_MIXER_VOLUME, 0x30, 7, 5, 0x31, 7, 5),
  MIX_ENT (SOUND_MIXER_BASS, 0x46, 7, 4, 0x47, 7, 4),
  MIX_ENT (SOUND_MIXER_TREBLE, 0x44, 7, 4, 0x45, 7, 4),
  MIX_ENT (SOUND_MIXER_SYNTH, 0x34, 7, 5, 0x35, 7, 5),
  MIX_ENT (SOUND_MIXER_PCM, 0x32, 7, 5, 0x33, 7, 5),
  MIX_ENT (SOUND_MIXER_SPEAKER, 0x3b, 7, 2, 0x00, 0, 0),
  MIX_ENT (SOUND_MIXER_LINE, 0x38, 7, 5, 0x39, 7, 5),
  MIX_ENT (SOUND_MIXER_MIC, 0x3a, 7, 5, 0x00, 0, 0),
  MIX_ENT (SOUND_MIXER_CD, 0x36, 7, 5, 0x37, 7, 5),
  MIX_ENT (SOUND_MIXER_IMIX, 0x3c, 0, 1, 0x00, 0, 0),
  MIX_ENT (SOUND_MIXER_LINE1, 0, 0, 0, 0, 0, 0),
  MIX_ENT (SOUND_MIXER_RECLEV, 0x3f, 7, 2, 0x40, 7, 2),
  MIX_ENT (SOUND_MIXER_IGAIN, 0x3f, 7, 2, 0x40, 7, 2),
  MIX_ENT (SOUND_MIXER_OGAIN, 0x41, 7, 2, 0x42, 7, 2)
};

/*ARGSUSED*/
static void
change_bits (cmpci_devc * devc, unsigned char *regval, int dev, int chn,
	     int newval)
{
  unsigned char mask;
  int shift;

  mask = (1 << cmpci_mix[dev][chn].nbits) - 1;
  newval = (int) ((newval * mask) + 50) / 100;	/* Scale */

  shift = cmpci_mix[dev][chn].bitoffs - cmpci_mix[dev][LEFT_CHN].nbits + 1;

  *regval &= ~(mask << shift);	/* Mask out previous value */
  *regval |= (newval & mask) << shift;	/* Set the new value */
}

static int set_recmask (int dev, int mask);

static int
cmpci_mixer_set (cmpci_devc * devc, int chan, int value)
{
  int left = value & 0x000000ff;
  int right = (value & 0x0000ff00) >> 8;

  int regoffs;
  unsigned char val;

  if (left > 100)
    left = 100;
  if (right > 100)
    right = 100;

  if (chan > 31)
    return -EINVAL;

  if (!(CMEDIA_MIXER_DEVICES & (1 << chan)))	/*
						 * Not supported
						 */
    return -EINVAL;

  regoffs = cmpci_mix[chan][LEFT_CHN].regno;

  if (regoffs == 0)
    return -EINVAL;

  val = cmpci_getmixer (devc, regoffs);
  change_bits (devc, &val, chan, LEFT_CHN, left);

  devc->levels[chan] = left | (left << 8);

  if (cmpci_mix[chan][RIGHT_CHN].regno != regoffs)	/*
							 * Change register
							 */
    {
      cmpci_setmixer (devc, regoffs, val);	/*
						 * Save the old one
						 */
      regoffs = cmpci_mix[chan][RIGHT_CHN].regno;

      if (regoffs == 0)
	return left | (left << 8);	/*
					 * Just left channel present
					 */

      val = cmpci_getmixer (devc, regoffs);	/*
						 * Read the new one
						 */
    }

  change_bits (devc, &val, chan, RIGHT_CHN, right);

  cmpci_setmixer (devc, regoffs, val);

  devc->levels[chan] = left | (right << 8);
  return left | (right << 8);
}

static int cmpci_outsw (int dev, int ctrl, unsigned int cmd, int value);

/*ARGSUSED*/
static int
cmpci_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
{
  cmpci_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;
	    if (val == SOUND_MASK_LINE1)
	      *arg = cmpci_outsw (dev, AUX_REC, SNDCTL_MIX_WRITE, 1);
	    else
	      *arg = cmpci_outsw (dev, AUX_REC, SNDCTL_MIX_WRITE, 0);
	    return *arg = set_recmask (dev, val);
	    break;

	  case SOUND_MIXER_LINE1:
	    val = *arg;
	    return *arg = cmpci_outsw (dev, AUX_LEVEL, SNDCTL_MIX_WRITE, val);
	    break;

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

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

	  case SOUND_MIXER_DEVMASK:
	    return *arg = CMEDIA_MIXER_DEVICES;
	    break;

	  case SOUND_MIXER_STEREODEVS:
	    return *arg = CMEDIA_MIXER_DEVICES &
	      ~(SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IMIX);
	    break;

	  case SOUND_MIXER_RECMASK:
	    return *arg = CMEDIA_RECORDING_DEVICES;
	    break;

	  case SOUND_MIXER_LINE1:
	    val = cmpci_outsw (dev, AUX_LEVEL, SNDCTL_MIX_READ, 0);
	    return *arg = val;
	    break;

	  case SOUND_MIXER_CAPS:
	    return *arg = SOUND_CAP_EXCL_INPUT;
	    break;

	  default:
	    return *arg = devc->levels[cmd & 0x1f];
	  }
    }
  else
    return -EINVAL;
}

static int
set_recmask (int dev, int mask)
{
  cmpci_devc *devc = mixer_devs[dev]->devc;
  int devmask = mask & CMEDIA_RECORDING_DEVICES;
  int i;
  unsigned char regimageL, regimageR;

  if (!devmask)
    devmask = SOUND_MASK_MIC;

  regimageL = regimageR = 0;
  for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
    if ((1 << i) & devmask)
      {
	regimageL |= cmpci_recmasks_L[i];
	regimageR |= cmpci_recmasks_R[i];
      }
  cmpci_setmixer (devc, CMPCI_IMASK_L, regimageL);
  cmpci_setmixer (devc, CMPCI_IMASK_R, regimageR);
  devc->recmask = devmask;
  return devc->recmask;
}

static int
cmpci_outsw (int dev, int ctrl, unsigned int cmd, int value)
{
/*
 * Access function for CMPCI mixer extension bits
 */
  cmpci_devc *devc = mixer_devs[dev]->devc;
  int left, right, tmp;

  if (cmd == SNDCTL_MIX_READ)
    {
      value = 0;
      switch (ctrl)
	{
	case MUTE_LINE:	/* Left line in to output connection */
	  value = (cmpci_getmixer (devc, 0x3c) & 0x18) ? 0 : 1;
	  break;

	case MUTE_CD:		/* Cd in to output connection */
	  value = (cmpci_getmixer (devc, 0x3c) & 0x06) ? 0 : 1;
	  break;

	case MUTE_MIC:		/* Mic in to output connection */
	  value = (cmpci_getmixer (devc, 0x3c) & 0x01) ? 0 : 1;
	  break;

	case MODE_4SPK:	/* 4Speaker out */
	  value = INL (devc->osdev, MISC_CTRL) & (1 << 26) ? 1 : 0;
	  break;

	case DUALDAC:		/* dual dac */
	  value = devc->dev_mode & DUALDAC_MODE ? 1 : 0;
	  break;

	case REAR2LINE:	/* rear to line in */
	  value = INB (devc->osdev, MIXER1) & (1 << 5) ? 1 : 0;
	  break;

	case CEN2LINE:		/* center to line in */
	  value = INL (devc->osdev, LEGACY_CTRL) & (1 << 14) ? 1 : 0;
	  break;

	case BASS2LINE:	/* basss to line in */
	  value = INL (devc->osdev, LEGACY_CTRL) & (1 << 13) ? 1 : 0;
	  break;

	case SPDIF_PLAY:	/* spdif out */
	  value = INL (devc->osdev, LEGACY_CTRL) & (1 << 23) ? 1 : 0;
	  break;

	case SPDIF_LOOP:	/* S/PDIF I/O Loop */
	  value = (INL (devc->osdev, FUNCTRL1) & (1 << 7)) ? 1 : 0;
	  break;

	case SPDIF_REC:	/* spdif record mode */
	  value = devc->dev_mode & SPDIFIN_MODE ? 1 : 0;
	  break;

	case SPDIF_IMON:	/* spdif input monitor */
	  value = INB (devc->osdev, MIXER1) & 0x1 ? 1 : 0;
	  break;

	case SPDIF_POL:	/* spdif input reverse */
	  if (devc->chiprev < 39)
	    value = INB (devc->osdev, MIXER3) & 0x06 ? 1 : 0;
	  else
	    value = INL (devc->osdev, CHFORMAT) & 0x80 ? 1 : 0;
	  break;

	case SPDIF_AC3:	/* ac3 */
	  value = INL (devc->osdev, MISC_CTRL) & (1 << 18) ? 1 : 0;
	  break;

	case SPDIF_COPY:	/* copy protect (indirect) */
	  value = INL (devc->osdev, LEGACY_CTRL) & (1 << 22) ? 1 : 0;
	  break;

	case SPDIF_OPT:	/* Coax/Optical Select */
	  value = INL (devc->osdev, MISC_CTRL) & (1 << 25) ? 1 : 0;
	  break;

	case CEN2MIC:		/* Center2MIC */
	  if (devc->chiprev >= 39)
	    value = INB (devc->osdev, MIXER3) & 0x4 ? 1 : 0;
	  break;

	case MICBOOST:		/* MIC Boost */
	  value = INB (devc->osdev, MIXER2) & 0x1 ? 0 : 1;
	  break;

	case AUX_LEVEL:
	  value = devc->levels[SOUND_MIXER_LINE1];
	  break;

	case AUX_REC:		/* set LINE1 as rec source - handled by set_recmask */
	  break;

	case MUTE_AUX:		/* AUX mute */
	  value = INB (devc->osdev, MIXER2) & 0x30 ? 0 : 1;
	  break;

	default:
	  return -EINVAL;
	}

      return value;
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (ctrl)
	{
	case MUTE_LINE:	/* L/R line in to output connection */
	  if (!value)
	    cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) | 0x18);
	  else
	    cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) & ~0x18);
	  break;

	case MUTE_CD:		/* Cd in to output connection */
	  if (!value)
	    cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) | 0x06);
	  else
	    cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) & ~0x06);
	  break;

	case MUTE_MIC:		/* Mic in to output connection */
	  if (!value)
	    cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) | 0x01);
	  else
	    cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) & ~0x01);
	  break;

	case MODE_4SPK:	/* 4Speaker out */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 26),
		    MISC_CTRL);
	      devc->mode_4spk = 1;
	    }
	  else
	    OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26),
		  MISC_CTRL);
	  devc->mode_4spk = 0;
	  break;

	case DUALDAC:		/* DUAL DAC mode */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 23),
		    MISC_CTRL);
	      /* Disable 4Speaker mode */
	      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26),
		    MISC_CTRL);
	      devc->dev_mode = DUALDAC_MODE;
	    }
	  else
	    {
	      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 23),
		    MISC_CTRL);

	      /* enable back the 4Speaker mode */
	      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | 1 << 26,
		    MISC_CTRL);

	      devc->dev_mode = DEFAULT_MODE;
	    }
	  break;

	case REAR2LINE:	/* REAR TO LINEIN */
	  if (value)
	    {
	      OUTB (devc->osdev, (INB (devc->osdev, MIXER1) | (1 << 5)),
		    MIXER1);
	    }
	  else
	    OUTB (devc->osdev, (INB (devc->osdev, MIXER1) & ~(1 << 5)),
		  MIXER1);
	  break;

	case CEN2LINE:		/* CENTER TO LINEIN */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 14),
		    LEGACY_CTRL);
	    }
	  else
	    OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 14),
		  LEGACY_CTRL);
	  break;

	case BASS2LINE:	/* BASS TO LINEIN */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 13),
		    LEGACY_CTRL);
	    }
	  else
	    OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 13),
		  LEGACY_CTRL);
	  break;

	case SPDIF_PLAY:	/* SPDIF ENABLE */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 23),
		    LEGACY_CTRL);

	      /* enable wave/fm/midi to spdif OUT DAC2SPDO on rev 33/37 */
	      if (devc->chiprev < 39)
		OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 21),
		      LEGACY_CTRL);
	      devc->spdif_enabled = 1;
	    }
	  else
	    {
	      OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 23),
		    LEGACY_CTRL);
	      /* Disable wave/fm/midi to spdif OUT (DAC2SPDO) */
	      if (devc->chiprev < 39)
		OUTL (devc->osdev,
		      INL (devc->osdev, LEGACY_CTRL) & ~(1 << 21),
		      LEGACY_CTRL);
	      devc->spdif_enabled = 0;
	    }
	  break;

	case SPDIF_LOOP:	/* S/PDIF I/O Loop */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) | (1 << 7),
		    FUNCTRL1);
	    }
	  else
	    OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) & ~(1 << 7),
		  FUNCTRL1);
	  break;

	case SPDIF_REC:	/* SPDIF Record Mode */
	  if (value)
	    {
	      devc->dev_mode = SPDIFIN_MODE;
	    }
	  else
	    devc->dev_mode = DEFAULT_MODE;
	  break;

	case SPDIF_IMON:	/*  spdif monitor */
	  if (value)
	    {
	      OUTB (devc->osdev, INB (devc->osdev, MIXER1) | 0x0D, MIXER1);
	    }
	  else
	    OUTB (devc->osdev, INB (devc->osdev, MIXER1) & ~0xD, MIXER1);
	  break;

	case SPDIF_POL:	/* spdif reverse */
	  if (value)
	    {
	      if (devc->chiprev < 39)
		{
		  OUTB (devc->osdev, INB (devc->osdev, MIXER3) | 0x06,
			MIXER3);
		}
	      else
		OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) | 0x80,
		      CHFORMAT);
	    }
	  else
	    {
	      if (devc->chiprev < 39)
		{
		  OUTB (devc->osdev, INB (devc->osdev, MIXER3) & ~0x06,
			MIXER3);
		}
	      else
		OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~0x80,
		      CHFORMAT);
	    }
	  break;

	case SPDIF_AC3:	/* AC3 enabled on S/PDIF */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 18),
		    MISC_CTRL);
	    }
	  else
	    OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 18),
		  MISC_CTRL);
	  break;

	case SPDIF_COPY:	/* Copy protect */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 22),
		    LEGACY_CTRL);
	    }
	  else
	    OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 22),
		  LEGACY_CTRL);
	  break;

	case SPDIF_OPT:	/* Coax/Optical */
	  if (value)
	    {
	      OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 25),
		    MISC_CTRL);
	    }
	  else
	    OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 25),
		  MISC_CTRL);
	  break;

	case CEN2MIC:		/* Center -> Mic OUT */
	  if (value)
	    {
	      if (devc->chiprev >= 39)
		{
		  OUTB (devc->osdev, INB (devc->osdev, MIXER3) | 0x4, MIXER3);
		}
	    }
	  else if (devc->chiprev >= 39)
	    {
	      OUTB (devc->osdev, INB (devc->osdev, MIXER3) & ~0x4, MIXER3);
	    }
	  break;

	case MICBOOST:		/* Mic Boost */
	  if (!value)
	    {
	      OUTB (devc->osdev, INB (devc->osdev, MIXER2) | 0x1, MIXER2);
	    }
	  else
	    OUTB (devc->osdev, INB (devc->osdev, MIXER2) & ~0x1, MIXER2);
	  break;

	case AUX_LEVEL:	/* Aux levels */
	  left = value & 0xff;
	  right = (value >> 8) & 0xff;
	  if (left > 100)
	    left = 100;
	  if (right > 100)
	    right = 100;
	  value = left | (right << 8);
	  left = mix_cvt[left];
	  right = mix_cvt[right];

	  tmp = ((right * ((1 << 4) - 1) / 100) << 4) |
	    (left * ((1 << 4) - 1) / 100);

	  OUTB (devc->osdev, tmp, AUX_VOL);
	  devc->levels[SOUND_MIXER_LINE1] = value;
	  break;

	case AUX_REC:		/* line1 record select */
	  if (value)
	    {
	      OUTB (devc->osdev, INB (devc->osdev, MIXER2) | 0xc0, MIXER2);
	    }
	  else
	    OUTB (devc->osdev, INB (devc->osdev, MIXER2) & ~0xc0, MIXER2);
	  break;

	case MUTE_AUX:		/* line1 mute control */
	  if (!value)
	    {
	      OUTB (devc->osdev, INB (devc->osdev, MIXER2) | 0x30, MIXER2);
	    }
	  else
	    OUTB (devc->osdev, INB (devc->osdev, MIXER2) & ~0x30, MIXER2);
	  break;

	default:
	  return -EINVAL;
	}

      return (value);
    }
  return -EINVAL;
}

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

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

  if ((err =
       mixer_ext_create_control (dev, group, MUTE_LINE, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_LINEMUTE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err =
       mixer_ext_create_control (dev, group, MUTE_CD, cmpci_outsw, MIXT_ONOFF,
				 "CMPCI_CDMUTE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err =
       mixer_ext_create_control (dev, group, MUTE_MIC, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_MICMUTE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err =
       mixer_ext_create_control (dev, group, MUTE_AUX, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_LINE1MUTE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((group = mixer_ext_create_group (dev, 0, "CMPCI_JACKCTL")) < 0)
    return group;
  if ((err =
       mixer_ext_create_control (dev, group, REAR2LINE, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_REAR2LINE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  if ((err =
       mixer_ext_create_control (dev, group, CEN2LINE, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_CEN2LINE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  if ((err =
       mixer_ext_create_control (dev, group, BASS2LINE, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_BASS2LINE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err =
       mixer_ext_create_control (dev, group, CEN2MIC, cmpci_outsw, MIXT_ONOFF,
				 "CMPCI_CEN2MIC", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;


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

  if ((err =
       mixer_ext_create_control (dev, group, MODE_4SPK, cmpci_outsw,
				 MIXT_ENUM, "CMPCI_SPKMODE", 2,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

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

  if ((err =
       mixer_ext_create_control (dev, group, MICBOOST, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_MICBOOST", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  /* Create a new SPDIF group */
  if ((group = mixer_ext_create_group (dev, 0, "CMPCI_SPDIF")) < 0)
    return group;

  if ((err =
       mixer_ext_create_control (dev, group, SPDIF_PLAY, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_Play", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err =
       mixer_ext_create_control (dev, group, SPDIF_LOOP, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_LOOP", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  /* Having this in mixer doesn't make any sense */
  if ((err =
       mixer_ext_create_control (dev, group, SPDIF_REC, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_RECORD", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err =
       mixer_ext_create_control (dev, group, SPDIF_IMON, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_IMON", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err =
       mixer_ext_create_control (dev, group, SPDIF_POL, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_POLREV", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

#if 0
  /* Having this in mixer doesn't make any sense */
  if ((err =
       mixer_ext_create_control (dev, group, SPDIF_AC3, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_AC3", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
#endif

  if ((err =
       mixer_ext_create_control (dev, group, SPDIF_COPY, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_COPYPROT", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err =
       mixer_ext_create_control (dev, group, SPDIF_OPT, cmpci_outsw,
				 MIXT_ONOFF, "CMPCI_OPTICAL", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  return 0;
}

static void
cmpci_mixer_reset (int dev)
{
  int i;
  cmpci_devc *devc = mixer_devs[dev]->devc;

  devc->levels = load_mixer_volumes ("CM8738_Mixer", default_levels, 1);
  devc->recmask = 0;

  for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
    cmpci_mixer_set (devc, i, devc->levels[i]);

  set_recmask (dev, SOUND_MASK_MIC);
}


static mixer_driver_t cmpci_mixer_driver = {
  cmpci_mixer_ioctl
};


static int
cmpciintr (oss_device_t * osdev)
{
  cmpci_devc *devc = (cmpci_devc *) osdev->devc;
  unsigned int intstat, intsrc;
  int i;
  int serviced = 0;

  /* see if this is our interrupt */
  intsrc = INL (devc->osdev, INT_STATUS);
  if (intsrc & (CM_CH0_INT | CM_CH1_INT))
    {
      /* Handle playback */
      serviced = 1;

      intstat = INB (devc->osdev, INT_HLDCLR + 2);

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

	  if (intstat & CM_CH1_INT)
	    {
	      /* do chan1 playback */
	      if ((portc->chan1_play) &&
		  (portc->trigger_bits & PCM_ENABLE_OUTPUT))
		{
		  OUTB (devc->osdev, intstat & ~CM_CH1_INT, INT_HLDCLR + 2);
		  OUTB (devc->osdev, intstat | CM_CH1_INT, INT_HLDCLR + 2);
		  oss_audio_outputintr (portc->audiodev, 0);
		}

	      /* do chan1 record */
	      if ((portc->chan1_rec) &&
		  (portc->trigger_bits & PCM_ENABLE_INPUT))
		{
		  OUTB (devc->osdev, intstat & ~CM_CH1_INT, INT_HLDCLR + 2);
		  OUTB (devc->osdev, intstat | CM_CH1_INT, INT_HLDCLR + 2);
		  oss_audio_inputintr (portc->audiodev, 0);
		}
	    }

	  if (intstat & CM_CH0_INT)
	    {
	      /* do chan0 playback */
	      if ((portc->chan0_play) &&
		  (portc->trigger_bits & PCM_ENABLE_OUTPUT))
		{
		  OUTB (devc->osdev, intstat & ~CM_CH0_INT, INT_HLDCLR + 2);
		  OUTB (devc->osdev, intstat | CM_CH0_INT, INT_HLDCLR + 2);
		  oss_audio_outputintr (portc->audiodev, 0);
		}

	      /* do chan0 record */
	      if ((portc->chan0_rec) &&
		  (portc->trigger_bits & PCM_ENABLE_INPUT))
		{
		  OUTB (devc->osdev, intstat & ~CM_CH0_INT, INT_HLDCLR + 2);
		  OUTB (devc->osdev, intstat | CM_CH0_INT, INT_HLDCLR + 2);
		  oss_audio_inputintr (portc->audiodev, 0);
		}
	    }
	}
    }

  if (intsrc & 0x10000)
    {
      serviced = 1;
      uart401_irq (&devc->uart401devc);
    }

  return serviced;
}

static int
cmpci_audio_set_rate (int dev, int arg)
{
  cmpci_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
cmpci_audio_set_channels (int dev, short arg)
{
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;

  if (devc->model == MDL_CM8768)
    {
      if ((arg != 1) && (arg != 2) && (arg != 4) && (arg != 6) && (arg != 8))
	return portc->channels;
    }
  else
    {
      if ((arg != 1) && (arg != 2) && (arg != 4) && (arg != 6))
	return portc->channels;
    }
  portc->channels = arg;

  return portc->channels;
}

static unsigned int
cmpci_audio_set_format (int dev, unsigned int arg)
{
  cmpci_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;
}

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

static void cmpci_audio_trigger (int dev, int state);

static void
cmpci_audio_reset (int dev)
{
  cmpci_audio_trigger (dev, 0);
}

static void
cmpci_audio_reset_input (int dev)
{
  cmpci_portc *portc = audio_engines[dev]->portc;
  cmpci_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
cmpci_audio_reset_output (int dev)
{
  cmpci_portc *portc = audio_engines[dev]->portc;
  cmpci_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
cmpci_audio_open (int dev, int mode, int open_flags)
{
  cmpci_portc *portc = audio_engines[dev]->portc;
  cmpci_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;
    }

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

      devc->open_mode |= mode;
    }
  portc->open_mode = mode;
  portc->audio_enabled &= ~mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static void
cmpci_audio_close (int dev, int mode)
{
  cmpci_portc *portc = audio_engines[dev]->portc;
  cmpci_devc *devc = audio_engines[dev]->devc;

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

  if ((devc->spdif_enabled) || (devc->dev_mode & SPDIFIN_MODE))
    OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) & ~(1 << 9), FUNCTRL1);

  if (portc->chan0_play)
    portc->chan0_play = 0;

  if (portc->chan1_play)
    portc->chan1_play = 0;

  if (portc->chan0_rec)
    portc->chan0_rec = 0;

  if (portc->chan1_rec)
    portc->chan1_rec = 0;
}

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

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

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

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

static void
cmpci_audio_trigger (int dev, int state)
{
  cmpci_portc *portc = audio_engines[dev]->portc;
  cmpci_devc *devc = audio_engines[dev]->devc;
  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->chan0_play)
		{
		  /* enable the channel0 */
		  OUTB (devc->osdev,
			INB (devc->osdev, FUNCTRL0 + 2) | CM_CH0_ENABLE,
			FUNCTRL0 + 2);
		  OUTB (devc->osdev,
			INB (devc->osdev, INT_HLDCLR + 2) | CM_CH0_INT,
			INT_HLDCLR + 2);
		}

	      if (portc->chan1_play)
		{
		  /* enable the channel1 */
		  OUTB (devc->osdev,
			INB (devc->osdev, FUNCTRL0 + 2) | CM_CH1_ENABLE,
			FUNCTRL0 + 2);
		  OUTB (devc->osdev,
			INB (devc->osdev, INT_HLDCLR + 2) | CM_CH1_INT,
			INT_HLDCLR + 2);
		}
	      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->chan0_play)
		{
		  /* disable interrupt */
		  OUTB (devc->osdev,
			INB (devc->osdev, INT_HLDCLR + 2) & ~CM_CH0_INT,
			INT_HLDCLR + 2);

		  /* disable channel0 */
		  OUTB (devc->osdev,
			INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH0_ENABLE,
			FUNCTRL0 + 2);
		}
	      if (portc->chan1_play)
		{
		  /* disable interrupt */
		  OUTB (devc->osdev,
			INB (devc->osdev, INT_HLDCLR + 2) & ~CM_CH1_INT,
			INT_HLDCLR + 2);

		  /* disable channel */
		  OUTB (devc->osdev,
			INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH1_ENABLE,
			FUNCTRL0 + 2);
		}
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      if (portc->chan1_rec)
		{
		  /* enable the channel1 */
		  OUTB (devc->osdev,
			INB (devc->osdev, FUNCTRL0 + 2) | CM_CH1_ENABLE,
			FUNCTRL0 + 2);
		  OUTB (devc->osdev,
			INB (devc->osdev, INT_HLDCLR + 2) | CM_CH1_INT,
			INT_HLDCLR + 2);
		}
	      if (portc->chan0_rec)
		{
		  /* enable the channel0 */
		  OUTB (devc->osdev,
			INB (devc->osdev, FUNCTRL0 + 2) | CM_CH0_ENABLE,
			FUNCTRL0 + 2);
		  OUTB (devc->osdev,
			INB (devc->osdev, INT_HLDCLR + 2) | CM_CH0_INT,
			INT_HLDCLR + 2);
		}
	      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;
	      if (portc->chan1_rec)
		{
		  /* disable interrupt */
		  OUTB (devc->osdev,
			INB (devc->osdev, INT_HLDCLR + 2) & ~CM_CH1_INT,
			INT_HLDCLR + 2);

		  /* disable channel 1 */
		  OUTB (devc->osdev,
			INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH1_ENABLE,
			FUNCTRL0 + 2);
		}
	      if (portc->chan0_rec)
		{
		  /* disable interrupt */
		  OUTB (devc->osdev,
			INB (devc->osdev, INT_HLDCLR + 2) & ~CM_CH0_INT,
			INT_HLDCLR + 2);

		  /* disable channel 0 */
		  OUTB (devc->osdev,
			INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH0_ENABLE,
			FUNCTRL0 + 2);

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

static void
set_dac_rate (int dev, int chan_type)
{
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;
  unsigned char freq = 4, val;
  int i;
  int rate = portc->speed;

  if (rate > 48000)
    rate = 48000;
  if (rate < 5512)
    rate = 5512;

  for (i = 0; i < sizeof (rate_lookup) / sizeof (rate_lookup[0]); i++)
    {
      if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper)
	{
	  rate = rate_lookup[i].rate;
	  freq = rate_lookup[i].freq;
	  break;
	}
    }
  if (chan_type == CHAN0)
    {
      val = INB (devc->osdev, FUNCTRL1 + 1) & ~0x1c;
      OUTB (devc->osdev, val | freq << 2, FUNCTRL1 + 1);
    }
  else
    {
      val = INB (devc->osdev, FUNCTRL1 + 1) & ~0xe0;
      OUTB (devc->osdev, val | freq << 5, FUNCTRL1 + 1);
    }
  if (devc->spdif_enabled)
    set_spdif_rate (devc, portc);
}

static void
set_dac_fmt (int dev, int chan_type)
{
  unsigned char val;
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;
  int channels = portc->channels;
  int bits = portc->bits;

  if (chan_type == CHAN0)
    {
      /* Set the format on Channl 0 */
      val = INB (devc->osdev, CHFORMAT) & ~0x3;

      if ((channels == 1) && (bits == 8))
	{
	  OUTB (devc->osdev, 0x00 | val, CHFORMAT);
	  portc->dacfmt = 0;
	}

      if ((channels == 2) && (bits == 8))
	{
	  OUTB (devc->osdev, 0x01 | val, CHFORMAT);
	  portc->dacfmt = 1;
	}

      if ((channels == 1) && (bits == 16))
	{
	  OUTB (devc->osdev, 0x02 | val, CHFORMAT);
	  portc->dacfmt = 1;
	}

      if ((channels > 1) && (bits == 16))
	{
	  OUTB (devc->osdev, 0x03 | val, CHFORMAT);
	  portc->dacfmt = 2;
	}
    }
  else
    {
      /* Set the format on Channel 1 */
      val = INB (devc->osdev, CHFORMAT) & ~0xC;

      if ((channels == 1) && (bits == 8))
	{
	  OUTB (devc->osdev, 0x00 | val, CHFORMAT);
	  portc->dacfmt = 0;
	}

      if ((channels == 2) && (bits == 8))
	{
	  OUTB (devc->osdev, 0x04 | val, CHFORMAT);
	  portc->dacfmt = 1;
	}

      if ((channels == 1) && (bits == 16))
	{
	  OUTB (devc->osdev, 0x08 | val, CHFORMAT);
	  portc->dacfmt = 1;
	}

      if ((channels > 1) && (bits == 16))
	{
	  OUTB (devc->osdev, 0x0C | val, CHFORMAT);
	  portc->dacfmt = 2;
	}
    }
}


static void
set_adc_rate (int dev, int chan_type)
{
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;
  unsigned char freq = 4, val;
  int i;
  int rate = portc->speed;

  if (rate > 48000)
    rate = 48000;
  if (rate < 5512)
    rate = 5512;
  for (i = 0; i < sizeof (rate_lookup) / sizeof (rate_lookup[0]); i++)
    {
      if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper)
	{
	  rate = rate_lookup[i].rate;
	  freq = rate_lookup[i].freq;
	  break;
	}
    }
  if (chan_type == CHAN1)
    {
      val = INB (devc->osdev, FUNCTRL1 + 1) & ~0xe0;
      OUTB (devc->osdev, val | freq << 5, FUNCTRL1 + 1);
    }
  else
    {
      val = INB (devc->osdev, FUNCTRL1 + 1) & ~0x1c;
      OUTB (devc->osdev, val | freq << 2, FUNCTRL1 + 1);
    }

  if (devc->dev_mode & SPDIFIN_MODE)
    set_spdif_rate (devc, portc);
}

static void
set_adc_fmt (int dev, int chan_type)
{
  unsigned char val;
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;
  int channels = portc->channels;
  int bits = portc->bits;

  if (chan_type == CHAN1)
    {
      /* Set the format on Channel 1 */
      val = INB (devc->osdev, CHFORMAT) & ~0xC;

      if ((channels == 1) && (bits == 8))
	{
	  OUTB (devc->osdev, 0x00 | val, CHFORMAT);
	  portc->adcfmt = 0;
	}

      if ((channels == 2) && (bits == 8))
	{
	  OUTB (devc->osdev, 0x04 | val, CHFORMAT);
	  portc->adcfmt = 1;
	}

      if ((channels == 1) && (bits == 16))
	{
	  OUTB (devc->osdev, 0x08 | val, CHFORMAT);
	  portc->adcfmt = 1;
	}

      if ((channels > 1) && (bits == 16))
	{
	  OUTB (devc->osdev, 0x0C | val, CHFORMAT);
	  portc->adcfmt = 2;
	}
    }
  else
    {
      /* Set the format on Channl 0 */
      val = INB (devc->osdev, CHFORMAT) & ~0x3;

      if ((channels == 1) && (bits == 8))
	{
	  OUTB (devc->osdev, 0x00 | val, CHFORMAT);
	  portc->adcfmt = 0;
	}

      if ((channels == 2) && (bits == 8))
	{
	  OUTB (devc->osdev, 0x01 | val, CHFORMAT);
	  portc->adcfmt = 1;
	}

      if ((channels == 1) && (bits == 16))
	{
	  OUTB (devc->osdev, 0x02 | val, CHFORMAT);
	  portc->adcfmt = 1;
	}

      if ((channels == 2) && (bits == 16))
	{
	  OUTB (devc->osdev, 0x03 | val, CHFORMAT);
	  portc->adcfmt = 2;
	}
    }
}

static void
setup_record (int dev, int chan_type)
{
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;

  if (chan_type == CHAN1)	/* SPDIF Record can only occur on CHAN1 */
    {
      /* reset and disable channel */
      OUTB (devc->osdev,
	    INB (devc->osdev, FUNCTRL0 + 2) | CM_CH1_RESET, FUNCTRL0 + 2);
      oss_udelay (10);
      OUTB (devc->osdev,
	    INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH1_RESET, FUNCTRL0 + 2);

      cmpci_outsw (devc->mixer_dev, SPDIF_PLAY, SNDCTL_MIX_WRITE, 0);

      OUTL (devc->osdev, dmap->dmabuf_phys, CH1_FRAME1);
      OUTW (devc->osdev, (dmap->bytes_in_use >> portc->adcfmt) - 1,
	    CH1_FRAME2);
      OUTW (devc->osdev, (dmap->fragment_size >> portc->adcfmt),
	    CH1_FRAME2 + 2);

      /* set channel 1 to record mode */
      OUTB (devc->osdev, INB (devc->osdev, FUNCTRL0) | CM_CH1_RECORD,
	    FUNCTRL0);
      portc->chan1_rec = 1;

      /* setup SPDIF in on CHAN A */
      OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) | (1 << 9), FUNCTRL1);
    }
  else				/* Normal PCM record on Channel 0 */
    {
      /* reset and disable channel */
      OUTB (devc->osdev,
	    INB (devc->osdev, FUNCTRL0 + 2) | CM_CH0_RESET, FUNCTRL0 + 2);
      oss_udelay (10);
      OUTB (devc->osdev,
	    INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH0_RESET, FUNCTRL0 + 2);

      OUTL (devc->osdev, dmap->dmabuf_phys, CH0_FRAME1);
      OUTW (devc->osdev, (dmap->bytes_in_use >> portc->adcfmt) - 1,
	    CH0_FRAME2);
      OUTW (devc->osdev, (dmap->fragment_size >> portc->adcfmt),
	    CH0_FRAME2 + 2);

      /* set channel 0 to record mode */
      OUTB (devc->osdev, INB (devc->osdev, FUNCTRL0) | CM_CH0_RECORD,
	    FUNCTRL0);
      portc->chan0_rec = 1;
    }
}

/*ARGSUSED*/
static int
cmpci_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  switch (devc->dev_mode)
    {
    case DEFAULT_MODE:
      set_adc_rate (dev, CHAN0);
      set_adc_fmt (dev, CHAN0);
      setup_record (dev, CHAN0);
      break;

    case DUALDAC_MODE:
      cmn_err (CE_WARN, "Cannot record because DUALDAC mode is ON.\n");
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return -EIO;

    case SPDIFIN_MODE:
      if (portc->speed < 44100)
	{
	  cmn_err (CE_WARN,
		   "Cannot record spdif at sampling rate less than 44.1Khz.\n");
	  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
	  return -EIO;
	}
      set_adc_rate (dev, CHAN1);
      set_adc_fmt (dev, CHAN1);
      setup_record (dev, CHAN1);
      break;
    }
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static void
setup_play (int dev, int chan_type)
{
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;

  if (chan_type == CHAN0)
    {
      /* reset channel */
      OUTB (devc->osdev,
	    INB (devc->osdev, FUNCTRL0 + 2) | CM_CH0_RESET, FUNCTRL0 + 2);
      oss_udelay (10);
      OUTB (devc->osdev,
	    INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH0_RESET, FUNCTRL0 + 2);
      oss_udelay (10);

      /* Now set the buffer address/sizes */
      OUTL (devc->osdev, dmap->dmabuf_phys, CH0_FRAME1);
      OUTW (devc->osdev, (dmap->bytes_in_use >> portc->dacfmt) - 1,
	    CH0_FRAME2);
      OUTW (devc->osdev, (dmap->fragment_size >> portc->dacfmt),
	    CH0_FRAME2 + 2);

      /* set channel 0 to play mode */
      OUTB (devc->osdev, INB (devc->osdev, FUNCTRL0) & CM_CH0_PLAY, FUNCTRL0);
      portc->chan0_play = 1;

      /* setup spdif output on CHAN A , disable CHAN B spdif */
      if (devc->spdif_enabled)
	{
	  OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) | (1 << 8),
		FUNCTRL1);
	  OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) & ~(1 << 9),
		FUNCTRL1);
	}
    }
  else
    {
      /* reset and disable channel */
      OUTB (devc->osdev,
	    INB (devc->osdev, FUNCTRL0 + 2) | CM_CH1_RESET, FUNCTRL0 + 2);
      oss_udelay (10);
      OUTB (devc->osdev,
	    INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH1_RESET, FUNCTRL0 + 2);
      oss_udelay (10);

      /* Handle 4/5/6 channel mode */
      if (portc->channels < 4)
	{
	  /* check if 4speaker mode is enabled from mixer or not */
	  if (devc->mode_4spk)
	    OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26),
		  MISC_CTRL);

	  /* disable 4channel mode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(1 << 29),
		CHFORMAT);
	  /* disable 5 channel mode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(0x80000000),
		CHFORMAT);
	  /* disable 6channel mode out CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 15),
		LEGACY_CTRL);
	  /* disable 8 channel decode on CHAN B - only for CMI8768 */
	  if (devc->model == MDL_CM8768)
	    OUTB (devc->osdev, INB (devc->osdev, MISC2_CTRL) & ~0x20,
		  MISC2_CTRL);
	  /* Set NXCNG */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(0x80000000),
		LEGACY_CTRL);
	}

      if ((portc->channels == 4) && (devc->chiprev > 37))
	{
	  /* disable 4 speaker mode */
	  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26),
		MISC_CTRL);
	  /* enable 4channel mode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) | (1 << 29),
		CHFORMAT);

	  /* disable 5 channel mode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(0x80000000),
		CHFORMAT);
	  /* disable 6channel mode out CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 15),
		LEGACY_CTRL);
	  /* disable center/bass channel */
	  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 7),
		MISC_CTRL);
	  /* disable bass */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 12),
		LEGACY_CTRL);

	  /* disable 8 channel decode on CHAN B - only for CMI8768 */
	  if (devc->model == MDL_CM8768)
	    OUTB (devc->osdev, INB (devc->osdev, MISC2_CTRL) & ~0x20,
		  MISC2_CTRL);
	  /* Set NXCNG */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(0x80000000),
		LEGACY_CTRL);
	}

      if ((portc->channels == 6) && (devc->chiprev > 37))
	{
	  /* disable 4 speaker mode */
	  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26),
		MISC_CTRL);
	  /* disable 4channel mode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(1 << 29),
		CHFORMAT);

	  /* enable center channel */
	  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 7),
		MISC_CTRL);
	  /* enable bass */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 12),
		LEGACY_CTRL);
	  /* enable 5 channel mode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) | (0x80000000),
		CHFORMAT);
	  /* enable 6 channel decode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 15),
		LEGACY_CTRL);

	  /* disable 8 channel decode on CHAN B - only for CMI8768 */
	  if (devc->model == MDL_CM8768)
	    OUTB (devc->osdev, INB (devc->osdev, MISC2_CTRL) & ~0x20,
		  MISC2_CTRL);

	  /* Set NXCNG */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (0x80000000),
		LEGACY_CTRL);
	}

      if ((portc->channels == 8) && (devc->model == MDL_CM8768))
	{
	  /* disable 4 speaker mode */
	  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26),
		MISC_CTRL);
	  /* disable 4channel mode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(1 << 29),
		CHFORMAT);

	  /* enable center channel */
	  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 7),
		MISC_CTRL);
	  /* enable bass channel */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 12),
		LEGACY_CTRL);
	  /* disable 5 channel mode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(0x80000000),
		CHFORMAT);
	  /* disable 6 channel decode on CHAN B */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 15),
		LEGACY_CTRL);

	  /* enable 8 channel decode on CHAN B */
	  OUTB (devc->osdev, INB (devc->osdev, MISC2_CTRL) | 0x20,
		MISC2_CTRL);
	  /* Set NXCNG */
	  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (0x80000000),
		LEGACY_CTRL);
	}

      /* Now set the buffer address/sizes */
      OUTL (devc->osdev, dmap->dmabuf_phys, CH1_FRAME1);
      OUTW (devc->osdev, (dmap->bytes_in_use >> portc->dacfmt) - 1,
	    CH1_FRAME2);
      OUTW (devc->osdev, (dmap->fragment_size >> portc->dacfmt),
	    CH1_FRAME2 + 2);


      /* set channel 1 to play mode */
      OUTB (devc->osdev, INB (devc->osdev, FUNCTRL0) & CM_CH1_PLAY, FUNCTRL0);
      portc->chan1_play = 1;

      /* setup spdif output on CHAN B , disable CHAN A spdif */
      if (devc->spdif_enabled)
	{
	  OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) | (1 << 9),
		FUNCTRL1);
	  OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) & ~(1 << 8),
		FUNCTRL1);
	}
    }
}

/*ARGSUSED*/
static int
cmpci_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  cmpci_devc *devc = audio_engines[dev]->devc;
  cmpci_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if ((portc->bits == AFMT_AC3) && devc->can_ac3)
    {
      portc->bits = 16;
      portc->channels = 2;
      cmpci_outsw (devc->mixer_dev, SPDIF_PLAY, SNDCTL_MIX_WRITE, 1);
      setup_ac3 (devc, portc, 1);
    }
  else
    setup_ac3 (devc, portc, 0);

  switch (devc->dev_mode)
    {
    case DEFAULT_MODE:
      /* set speed */
      set_dac_rate (dev, CHAN1);
      /* set format */
      set_dac_fmt (dev, CHAN1);
      /* set buffer address/size and other setups */
      setup_play (dev, CHAN1);
      break;

    case DUALDAC_MODE:
      if (dev == devc->portc[0].audiodev)
	{
	  set_dac_rate (dev, CHAN1);
	  set_dac_fmt (dev, CHAN1);
	  setup_play (dev, CHAN1);
	  setup_ac3 (devc, portc, 0);
	}
      if (dev == devc->portc[1].audiodev)
	{
	  set_dac_rate (dev, CHAN0);
	  set_dac_fmt (dev, CHAN0);
	  setup_play (dev, CHAN0);
	  setup_ac3 (devc, portc, 0);
	}
      break;

    case SPDIFIN_MODE:
      set_dac_rate (dev, CHAN0);
      set_dac_fmt (dev, CHAN0);
      setup_play (dev, CHAN0);
      setup_ac3 (devc, portc, 0);
      break;
    }

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

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

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

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_INPUT)
    {
      if (portc->chan0_rec)
	ptr = INW (devc->osdev, CH0_FRAME1);
      if (portc->chan1_rec)
	ptr = INW (devc->osdev, CH1_FRAME1);
    }

  if (direction == PCM_ENABLE_OUTPUT)
    {
      if (portc->chan0_play)
	ptr = INW (devc->osdev, CH0_FRAME1);
      if (portc->chan1_play)
	ptr = INW (devc->osdev, CH1_FRAME1);
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr % dmap->bytes_in_use;
}

static audiodrv_t cmpci_audio_driver = {
  cmpci_audio_open,
  cmpci_audio_close,
  cmpci_audio_output_block,
  cmpci_audio_start_input,
  cmpci_audio_ioctl,
  cmpci_audio_prepare_for_input,
  cmpci_audio_prepare_for_output,
  cmpci_audio_reset,
  NULL,
  NULL,
  cmpci_audio_reset_input,
  cmpci_audio_reset_output,
  cmpci_audio_trigger,
  cmpci_audio_set_rate,
  cmpci_audio_set_format,
  cmpci_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* cmpci_alloc_buffer, */
  NULL,				/* cmpci_free_buffer, */
  NULL,
  NULL,
  cmpci_get_buffer_pointer
};

#ifdef OBSOLETED_STUFF
static void
attach_mpu (cmpci_devc * devc)
{
  unsigned int base;

  if (devc->chiprev == 33)
    base = 0x330;		/* Chiprev033 doen't have bas+0x40 */
  else
    base = MPU_MIRROR;		/* base+0x40 is MPU PCI mirror */

  uart401_init (&devc->uart401devc, devc->osdev, base, "Cmedia MIDI UART");
  devc->uart401_attached = 1;
}
#endif

static int
init_cmpci (cmpci_devc * devc)
{
  oss_native_word val;
  int first_dev = 0;
  int i;

  devc->fm_attached = 0;

/*
 * Enable BusMasterMode and IOSpace Access
 */
  /* Check the model number of the chip */
  val = INL (devc->osdev, INT_HLDCLR) & 0xff000000;

  if (!val)
    {
      val = INL (devc->osdev, CHFORMAT) & 0x1f000000;
      if (!val)
	{
	  devc->chiprev = 33;
	  devc->can_ac3 = 0;
	  devc->max_channels = 6;
	}
      else
	{
	  devc->chiprev = 37;
	  devc->can_ac3 = 1;
	  devc->max_channels = 6;
	}
    }
  else
    {
      if (val & 0x04000000)
	{
	  devc->chiprev = 39;
	  devc->can_ac3 = 1;
	  devc->max_channels = 6;
	}
      if (val & 0x08000000)
	{
	  devc->chiprev = 55;
	  devc->can_ac3 = 1;
	  devc->max_channels = 6;
	}
      if (val & 0x28000000)
	{
	  devc->chiprev = 68;
	  devc->can_ac3 = 1;
	  devc->model = MDL_CM8768;
	  devc->max_channels = 8;
	  devc->chip_name = "CMedia CM8768";
	}
    }

  /* enable uart, joystick in Function Control Reg1 */
  OUTB (devc->osdev, INB (devc->osdev, FUNCTRL1) | 0x06, FUNCTRL1);
  /* enable FM */
  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 19), MISC_CTRL);
  OUTB (devc->osdev, 0, INT_HLDCLR + 2);	/* disable ints */
  OUTB (devc->osdev, 0, FUNCTRL0 + 2);	/* reset channels */

#ifdef OBSOLETED_STUFF
  attach_mpu (devc);
#endif

  /* install the CMPCI mixer */
  if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
					    devc->osdev,
					    devc->osdev,
					    "CMedia CMPCI",
					    &cmpci_mixer_driver,
					    sizeof (mixer_driver_t),
					    devc)) < 0)
    {
      return 0;
    }

  mixer_devs[devc->mixer_dev]->devc = devc;
  mixer_devs[devc->mixer_dev]->hw_devc = devc;
  mixer_devs[devc->mixer_dev]->priority = 1;	/* Possible default mixer candidate */

  cmpci_mixer_reset (devc->mixer_dev);
  mixer_ext_set_init_fn (devc->mixer_dev, cmpci_mix_init, 25);
  OUTB (devc->osdev, 0xF, MIXER2);

  /* setup 4speaker output */
  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 26), MISC_CTRL);
  /* enable subwoofer/center channel */
  OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 12), LEGACY_CTRL);
  OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 7), MISC_CTRL);

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

      if (i == 0)
	{
	  sprintf (tmp_name, "%s (rev %0d)", devc->chip_name, devc->chiprev);
	  caps |= ADEV_DUPLEX;
	}
      else
	{
	  sprintf (tmp_name, "%s (playback only)", devc->chip_name);
	  caps |= ADEV_NOINPUT;
	}
      if ((portc->audiodev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
						   devc->osdev,
						   devc->osdev,
						   tmp_name,
						   &cmpci_audio_driver,
						   sizeof (audiodrv_t),
						   caps,
						   AFMT_U8 | AFMT_S16_LE |
						   AFMT_AC3, NULL, -1)) < 0)
	{
	  return 0;
	}
      else
	{
	  if (i == 0)
	    first_dev = portc->audiodev;
	  audio_engines[portc->audiodev]->devc = devc;
	  audio_engines[portc->audiodev]->portc = portc;
	  audio_engines[portc->audiodev]->rate_source = first_dev;
	  audio_engines[portc->audiodev]->caps =
	    PCM_CAP_ANALOGOUT | PCM_CAP_ANALOGIN | PCM_CAP_DIGITALOUT |
	    PCM_CAP_DIGITALIN;
	  audio_engines[portc->audiodev]->min_rate = 5000;
	  audio_engines[portc->audiodev]->max_rate = 48000;
	  audio_engines[portc->audiodev]->caps |= PCM_CAP_FREERATE;
	  audio_engines[portc->audiodev]->min_channels = 2;
	  audio_engines[portc->audiodev]->max_channels = devc->max_channels;
	  audio_engines[portc->audiodev]->vmix_flags = VMIX_MULTIFRAG;
	  audio_engines[portc->audiodev]->dmabuf_alloc_flags |=
	    DMABUF_SIZE_16BITS;
	  portc->open_mode = 0;
	  portc->audio_enabled = 0;
	}

      audio_engines[portc->audiodev]->mixer_dev = devc->mixer_dev;
      devc->dev_mode = DEFAULT_MODE;
      devc->spdif_enabled = 0;
    }
  return 1;
}

int
cmpci_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  int err;
  cmpci_devc *devc;

  DDB (cmn_err (CE_CONT, "Entered CMEDIA CMPCI attach routine\n"));
  pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
  pci_read_config_word (osdev, PCI_DEVICE_ID, &device);

  if (vendor != CMEDIA_VENDOR_ID
      || ((device != CMEDIA_CM8738) && (device != CMEDIA_CM8338A)
	  && (device != CMEDIA_CM8338B)))
    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);

  DDB (cmn_err (CE_WARN, "CMPCI 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->irq = pci_irq_line;

  /* Map the IO Base address */
  devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);

  /* Remove I/O space marker in bit 0. */
  devc->base &= ~3;

  /* set the PCI_COMMAND register to master mode */
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  switch (device)
    {
    case CMEDIA_CM8738:
      devc->model = MDL_CM8738;
      devc->chip_name = "CMedia CM8738";
      devc->max_channels = 6;
      break;

    case CMEDIA_CM8338A:
      devc->model = MDL_CM8338A;
      devc->chip_name = "CMedia CM8338A";
      devc->max_channels = 6;
      break;

    case CMEDIA_CM8338B:
      devc->model = MDL_CM8338B;
      devc->chip_name = "CMedia CM8338B";
      devc->max_channels = 6;
      break;
    }

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

  oss_register_device (osdev, devc->chip_name);

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

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

int
cmpci_detach (oss_device_t * osdev)
{
  cmpci_devc *devc = (cmpci_devc *) osdev->devc;

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

  /* disable Interrupts */
  OUTB (devc->osdev, 0, INT_HLDCLR + 2);

  /* disable channels */
  OUTB (devc->osdev, 0, FUNCTRL0 + 2);

  /* uninstall UART401 */
  if (devc->uart401_attached)
    uart401_disable (&devc->uart401devc);

  oss_unregister_interrupts (devc->osdev);

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

  oss_unregister_device (osdev);
  return 1;
}
