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


#include "oss_cmi878x_cfg.h"
#include <oss_pci.h>
#include <uart401.h>
#include <ac97.h>

#define CMEDIA_VENDOR_ID	0x13F6
#define CMEDIA_CMI8788		0x8788

/*
 * CM8338 registers definition
 */

#define RECA_ADDR		(devc->base+0x00)
#define RECA_SIZE		(devc->base+0x04)
#define RECA_FRAG		(devc->base+0x06)
#define RECB_ADDR		(devc->base+0x08)
#define RECB_SIZE		(devc->base+0x0C)
#define RECB_FRAG		(devc->base+0x0E)
#define RECC_ADDR		(devc->base+0x10)
#define RECC_SIZE		(devc->base+0x14)
#define RECC_FRAG		(devc->base+0x16)
#define SPDIF_ADDR		(devc->base+0x18)
#define SPDIF_SIZE		(devc->base+0x1C)
#define SPDIF_FRAG		(devc->base+0x1E)
#define MULTICH_ADDR		(devc->base+0x20)
#define MULTICH_SIZE		(devc->base+0x24)
#define MULTICH_FRAG		(devc->base+0x28)
#define FPOUT_ADDR		(devc->base+0x30)
#define FPOUT_SIZE		(devc->base+0x34)
#define FPOUT_FRAG		(devc->base+0x36)

#define DMA_START		(devc->base+0x40)
#define CHAN_RESET		(devc->base+0x42)
#define MULTICH_MODE		(devc->base+0x43)
#define IRQ_MASK		(devc->base+0x44)
#define IRQ_STAT		(devc->base+0x46)
#define MISC_REG		(devc->base+0x48)
#define REC_FORMAT		(devc->base+0x4A)
#define PLAY_FORMAT		(devc->base+0x4B)
#define REC_MODE		(devc->base+0x4C)
#define FUNCTION		(devc->base+0x50)

#define I2S_MULTICH_FORMAT	(devc->base+0x60)
#define I2S_ADC1_FORMAT		(devc->base+0x62)
#define I2S_ADC2_FORMAT		(devc->base+0x64)
#define I2S_ADC3_FORMAT		(devc->base+0x66)

#define SPDIF_FUNC		(devc->base+0x70)
#define SPDIFOUT_CHAN_STAT	(devc->base+0x74)
#define SPDIFIN_CHAN_STAT	(devc->base+0x78)

#define I2C_ADDR		(devc->base+0x90)
#define I2C_MAP		(devc->base+0x91)
#define I2C_DATA		(devc->base+0x92)
#define I2C_CTRL		(devc->base+0x94)

#define SPI_CONTROL		(devc->base+0x98)
#define SPI_DATA		(devc->base+0x99)

#define MPU401_DATA		(devc->base+0xA0)
#define MPU401_COMMAND		(devc->base+0xA1)
#define MPU401_CONTROL		(devc->base+0xA2)

#define GPI_DATA		(devc->base+0xA4)
#define GPI_IRQ_MASK		(devc->base+0xA5)
#define GPIO_DATA		(devc->base+0xA6)
#define GPIO_CONTROL		(devc->base+0xA8)
#define GPIO_IRQ_MASK		(devc->base+0xAA)
#define DEVICE_SENSE		(devc->base+0xAC)

#define PLAY_ROUTING		(devc->base+0xC0)
#define REC_ROUTING		(devc->base+0xC2)
#define REC_MONITOR		(devc->base+0xC3)
#define MONITOR_ROUTING		(devc->base+0xC4)

#define AC97_CTRL		(devc->base+0xD0)
#define AC97_INTR_MASK		(devc->base+0xD2)
#define AC97_INTR_STAT		(devc->base+0xD3)
#define AC97_OUT_CHAN_CONFIG	(devc->base+0xD4)
#define AC97_IN_CHAN_CONFIG	(devc->base+0xD8)
#define AC97_CMD_DATA		(devc->base+0xDC)

#define CODEC_VERSION		(devc->base+0xE4)
#define CTRL_VERSION		(devc->base+0xE6)

/* Device IDs */
#define ASUS_VENDOR_ID		0x1043
#define SUBID_XONAR_D2		0x8269
#define SUBID_XONAR_D2X		0x82b7
#define SUBID_XONAR_D1		0x834f
#define SUBID_XONAR_DX		0x8275
#define SUBID_XONAR_STX 	0x835c
#define SUBID_XONAR_DS		0x838e
#define SUBID_XONAR_ST		0x835d

#define SUBID_GENERIC		0x0000

/* Xonar specific */
#define XONAR_DX_FRONTDAC	0x9e
#define XONAR_DX_SURRDAC	0x30
#define XONAR_STX_FRONTDAC	0x98
#define XONAR_ST_FRONTDAC	0x98
#define XONAR_ST_CLOCK		0x9c
#define XONAR_DS_FRONTDAC	0x1
#define XONAR_DS_SURRDAC	0x0
#define XONAR_MCLOCK_256	0x10
#define XONAR_MCLOCK_512	0x20

/* defs for AKM 4396 DAC */
#define AK4396_CTL1        0x00
#define AK4396_CTL2        0x01
#define AK4396_CTL3        0x02
#define AK4396_LchATTCtl   0x03
#define AK4396_RchATTCtl   0x04

/* defs for CS4398 DAC */
#define CS4398_CHIP_ID	  0x01
#define CS4398_MODE_CTRL  0x02
#define CS4398_MIXING	  0x03
#define CS4398_MUTE_CTRL  0x04
#define CS4398_VOLA       0x05
#define CS4398_VOLB       0x06
#define CS4398_RAMP_CTRL  0x07
#define CS4398_MISC_CTRL  0x08
#define CS4398_MISC2_CTRL 0x09

#define CS4398_POWER_DOWN (1<<7)	/* Obvious */
#define CS4398_CPEN	  (1<<6)	/* Control Port Enable */
#define CS4398_FREEZE	  (1<<5)	/* Freezes registers, unfreeze to 
					 * accept changed registers */
#define CS4398_MCLKDIV2   (1<<4)	/* Divide MCLK by 2 */
#define	CS4398_MCLKDIV3   (1<<3)	/* Divive MCLK by 3 */
#define CS4398_I2S	  (1<<4)	/* Set I2S mode */

/* defs for CS4362A DAC */
#define CS4362A_MODE1_CTRL 	0x01
#define CS4362A_MODE2_CTRL	0x02
#define CS4362A_MODE3_CTRL	0x03
#define CS4362A_FILTER_CTRL	0x04
#define CS4362A_INVERT_CTRL	0x05
#define CS4362A_MIX1_CTRL	0x06
#define CS4362A_VOLA_1		0x07
#define CS4362A_VOLB_1		0x08
#define CS4362A_MIX2_CTRL	0x09
#define CS4362A_VOLA_2		0x0A
#define CS4362A_VOLB_2		0x0B
#define CS4362A_MIX3_CTRL	0x0C
#define CS4362A_VOLA_3		0x0D
#define CS4362A_VOLB_3		0x0E
#define CS4362A_CHIP_REV	0x12

/* CS4362A Reg 01h */
#define CS4362A_CPEN		(1<<7)
#define CS4362A_FREEZE		(1<<6)
#define CS4362A_MCLKDIV		(1<<5)
#define CS4362A_DAC3_ENABLE	(1<<3)
#define CS4362A_DAC2_ENABLE	(1<<2)
#define CS4362A_DAC1_ENABLE	(1<<1)
#define CS4362A_POWER_DOWN	(1)

/* CS4362A Reg 02h */
#define CS4362A_DIF_LJUST	0x00
#define CS4362A_DIF_I2S		0x10
#define CS4362A_DIF_RJUST16	0x20
#define CS4362A_DIF_RJUST24	0x30
#define CS4362A_DIF_RJUST20	0x40
#define CS4362A_DIF_RJUST18	0x50

/* CS4362A Reg 03h */
#define CS4362A_RAMP_IMMEDIATE  0x00
#define CS4362A_RAMP_ZEROCROSS	0x40
#define CS4362A_RAMP_SOFT	0x80
#define CS4362A_RAMP_SOFTZERO   0xC0
#define CS4362A_SINGLE_VOL	0x20
#define CS4362A_RAMP_ERROR	0x10
#define CS4362A_MUTEC_POL	0x08
#define CS4362A_AUTOMUTE	0x04
#define CS4362A_SIX_MUTE	0x00
#define CS4362A_ONE_MUTE	0x01
#define CS4362A_THREE_MUTE	0x03

/* CS4362A Reg 04h */
#define CS4362A_FILT_SEL	0x10
#define CS4362A_DEM_NONE	0x00
#define CS4362A_DEM_44KHZ	0x02
#define CS4362A_DEM_48KHZ	0x04
#define CS4362A_DEM_32KHZ	0x06
#define CS4362A_RAMPDOWN	0x01

/* CS4362A Reg 05h */
#define CS4362A_INV_A3 		(1<<4)
#define CS4362A_INV_B3 		(1<<5)
#define CS4362A_INV_A2		(1<<2)
#define CS4362A_INV_B2		(1<<3)
#define CS4362A_INV_A1		(1)
#define CS4362A_INV_B1		(1<<1)

/* CS4362A Reg 06h, 09h, 0Ch */
/* ATAPI crap, does anyone still use analog CD playback? */

/* CS4362A Reg 07h, 08h, 0Ah, 0Bh, 0Dh, 0Eh */
/* Volume registers */
#define CS4362A_VOL_MUTE	0x80


/* 0-100. Start at -96dB. */
#define CS4398_VOL(x) \
	((x) == 0 ? 0xFF : (0xC0 - ((x)*192/100)))
/* 0-100. Start at -96dB. Bit 7 is mute. */
#define CS4362A_VOL(x) \
	(char)((x) == 0 ? 0xFF : (0x60 - ((x)*96/100)))

#define UNUSED_CMI9780_CONTROLS ( \
        SOUND_MASK_VOLUME | \
        SOUND_MASK_PCM | \
        SOUND_MASK_REARVOL | \
        SOUND_MASK_CENTERVOL | \
        SOUND_MASK_SIDEVOL | \
        SOUND_MASK_SPEAKER | \
        SOUND_MASK_ALTPCM | \
        SOUND_MASK_VIDEO | \
        SOUND_MASK_DEPTH | \
        SOUND_MASK_MONO | \
        SOUND_MASK_PHONE \
        )

typedef struct cmi8788_portc
{
  int speed, bits, channels;
  int open_mode;
  int trigger_bits;
  int audio_enabled;
  int audiodev;
  int dac_type;
#define ADEV_MULTICH	0
#define ADEV_FRONTPANEL	2
#define ADEV_SPDIF	3
  int adc_type;
#define ADEV_I2SADC1	0
#define ADEV_I2SADC2	1
#define ADEV_I2SADC3	2
  int play_dma_start, rec_dma_start;
  int play_irq_mask, rec_irq_mask;
  int play_chan_reset, rec_chan_reset;
  int min_rate, max_rate, min_chan, max_chan;
}
cmi8788_portc;

#define MAX_PORTC 4

typedef struct cmi8788_devc
{
  oss_device_t *osdev;
  oss_native_word base;
  int fm_attached;
  int irq;
  int dma_len;
  volatile unsigned char intr_mask;
  int model;
#define MDL_CMI8788		1
  char *chip_name;

  /* Audio parameters */
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;
  oss_mutex_t dac_mutex;
  int open_mode;
  cmi8788_portc portc[MAX_PORTC];

  /* Mixer */
  ac97_devc ac97devc, fp_ac97devc;
  int ac97_mixer_dev, fp_mixer_dev, cmi_mixer_dev;
  int playvol[4];
  int recvol;
  int mute;

  /* uart401 */
  oss_midi_inputbyte_t midi_input_intr;
  int midi_opened, midi_disabled;
  volatile unsigned char input_byte;
  int midi_dev;
  int mpu_attached;
}
cmi8788_devc;

static const char xd2_codec_map[4] = {
                0, 1, 2, 4
        };

static void cmi8788uartintr (cmi8788_devc * devc);
static int reset_cmi8788uart (cmi8788_devc * devc);
static void enter_uart_mode (cmi8788_devc * devc);

static int
ac97_read (void *devc_, int reg)
{
  cmi8788_devc *devc = devc_;
  oss_native_word flags;
  int val, data;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  val = 0L;
  val |= reg << 16;
  val |= 1 << 23;		/*ac97 read the reg address */
  val |= 0 << 24;		/*codec 0 */
  OUTL (devc->osdev, val, AC97_CMD_DATA);
  oss_udelay (200);
  data = INL (devc->osdev, AC97_CMD_DATA) & 0xFFFF;
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return data;
}

static int
ac97_write (void *devc_, int reg, int data)
{
  cmi8788_devc *devc = devc_;
  oss_native_word flags;
  int val;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  val = 0L;
  val |= data & 0xFFFF;
  val |= reg << 16;
  val |= 0 << 23;		/*ac97 write operation */
  val |= 0 << 24;		/*on board codec */
  OUTL (devc->osdev, val, AC97_CMD_DATA);
  oss_udelay (200);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 1;
}

static int
fp_ac97_read (void *devc_, int reg)
{
  cmi8788_devc *devc = devc_;
  oss_native_word flags;
  int data, val;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  val = 0L;
  val |= reg << 16;
  val |= 1 << 23;		/*ac97 read the reg address */
  val |= 1 << 24;		/*fp codec1 */
  OUTL (devc->osdev, val, AC97_CMD_DATA);
  oss_udelay (200);
  data = INL (devc->osdev, AC97_CMD_DATA) & 0xFFFF;
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return data;
}

static int
fp_ac97_write (void *devc_, int reg, int data)
{
  cmi8788_devc *devc = devc_;
  oss_native_word flags;
  int val;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  val = 0L;
  val |= data & 0xFFFF;
  val |= reg << 16;
  val |= 0 << 23;		/*ac97 write operation */
  val |= 1 << 24;		/*fp codec1 */
  OUTL (devc->osdev, val, AC97_CMD_DATA);
  oss_udelay (200);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 1;
}

static int
spi_write (cmi8788_devc *devc, int codec_num, unsigned char reg, int val)
{
  oss_native_word flags;
  unsigned int tmp;
  int latch, shift, count;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  /* check if SPI is busy */
   count = 10;
   while ((INB(devc->osdev, SPI_CONTROL) & 0x1) && count-- > 0) {
    	oss_udelay(10);
  }

  if (devc->model == SUBID_XONAR_DS) {
	shift = 9;
	latch = 0;
  }
  else {
	shift = 8;
	latch = 0x80;
  }

  /* 2 byte data/reg info to be written */
  tmp = val;
  tmp |= (reg << shift);

  /* write 2-byte data values */
  OUTB (devc->osdev, tmp & 0xff, SPI_DATA + 0);
  OUTB (devc->osdev, (tmp >> 8) & 0xff, SPI_DATA + 1);

  /* Latch high, clock=160, Len=2byte, mode=write */
  tmp = (INB (devc->osdev, SPI_CONTROL) & ~0x7E) | latch | 0x1;

  /* now address which codec you want to send the data to */
  tmp |= (codec_num << 4);

  /* send the command to write the data */
  OUTB (devc->osdev, tmp, SPI_CONTROL);

  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 1;
}

static int
i2c_write (cmi8788_devc *devc, unsigned char codec_num, unsigned char reg,
		unsigned char data)
{
  oss_native_word flags;
  int count = 50;

  /* Wait for it to stop being busy */
  MUTEX_ENTER(devc->dac_mutex, flags);
  while((INW(devc->osdev, I2C_CTRL) & 0x1) && (count > 0))
  {
	oss_udelay(10);
	count--;
  }
  if(count == 0)
  {
	cmn_err(CE_WARN, "Time out on Two-Wire interface (busy).");
  	MUTEX_EXIT(devc->dac_mutex, flags);
	return OSS_EIO;
  }
  MUTEX_EXIT(devc->dac_mutex, flags);

  MUTEX_ENTER_IRQDISABLE(devc->low_mutex, flags);
  /* first write the Register Address into the MAP register */
  OUTB (devc->osdev, reg, I2C_MAP);

  /* now write the data */
  OUTB (devc->osdev, data, I2C_DATA);

  /* select the codec number to address */
  OUTB (devc->osdev, codec_num, I2C_ADDR);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  oss_udelay(100); 

  return 1;

}

static void
cs4398_init (cmi8788_devc *devc, int codec_addr)
{

  /* Fast Two-Wire. Reduces the wire ready time. */
  OUTW(devc->osdev, 0x0100, I2C_CTRL);

  /* Power down, enable control mode.
   * i2c_write(devc, codec_addr, CS4398_MISC_CTRL, 
   * CS4398_CPEN | CS4398_POWER_DOWN);
   * Left justified PCM (DAC and 8788 support I2S, but doesn't work.
   * Setting it introduces clipping like hell).
   */
  i2c_write(devc, codec_addr, CS4398_MODE_CTRL, 0);
  /* That's the DAC default, set anyway.  */
  i2c_write(devc, codec_addr, 3, 0x09);
  /* PCM auto-mute. */
  i2c_write(devc, codec_addr, 4, 0x82);
  /* Vol A+B to -64dB. */
  i2c_write(devc, codec_addr, 5, 0x80);
  i2c_write(devc, codec_addr, 6, 0x80);
  /* Soft-ramping. */
  i2c_write(devc, codec_addr, 7, 0xF0);
  /* Remove power down flag. */
  i2c_write(devc, codec_addr, CS4398_MISC_CTRL, CS4398_CPEN);
}

static void
cs4362a_init(cmi8788_devc *devc, int codec_addr)
{
  
  /* Fast Two-Wire. Reduces the wire ready time. */
  OUTW(devc->osdev, 0x0100, I2C_CTRL);

  /* Power down and enable control port. */ 
  i2c_write(devc, codec_addr, CS4362A_MODE1_CTRL, CS4362A_CPEN | CS4362A_POWER_DOWN);
  /* Left-justified PCM */
  i2c_write(devc, codec_addr, CS4362A_MODE2_CTRL, CS4362A_DIF_LJUST);
  /* Ramp & Automute, re-set DAC defaults. */
  i2c_write(devc, codec_addr, CS4362A_MODE3_CTRL, 0x84); 
  /* Filter control, DAC defs. */
  i2c_write(devc, codec_addr, CS4362A_FILTER_CTRL, 0);
  /* Invert control, DAC defs. */
  i2c_write(devc, codec_addr, CS4362A_INVERT_CTRL, 0);
  /* Mixing control, DAC defs. */
  i2c_write(devc, codec_addr, CS4362A_MIX1_CTRL, 0x24);
  i2c_write(devc, codec_addr, CS4362A_MIX2_CTRL, 0x24);
  i2c_write(devc, codec_addr, CS4362A_MIX3_CTRL, 0x24);
  /* Volume to -64dB. */
  i2c_write(devc, codec_addr, CS4362A_VOLA_1, 0x40);
  i2c_write(devc, codec_addr, CS4362A_VOLB_1, 0x40);
  i2c_write(devc, codec_addr, CS4362A_VOLA_2, 0x40);
  i2c_write(devc, codec_addr, CS4362A_VOLB_2, 0x40);
  i2c_write(devc, codec_addr, CS4362A_VOLA_3, 0x40);
  i2c_write(devc, codec_addr, CS4362A_VOLB_3, 0x40);
  /* Power up. */
  i2c_write(devc, codec_addr, CS4362A_MODE1_CTRL, CS4362A_CPEN);
}


static void
cs2000_init(cmi8788_devc *devc, int addr)
{

  /* Fast Two-Wire. Reduces the wire ready time. */
  OUTW(devc->osdev, 0x0100, I2C_CTRL);

  /* first set global config reg  to freeze */
  i2c_write (devc, addr, 0x5, 0x08); 

  /* set dev ctrl reg to  output enabled (0) */
  i2c_write (devc, addr, 0x2, 0);

  /* set dev cfg1 reg to  defaults and enable */
  i2c_write (devc, addr, 0x3, 0x0 | (0 << 3) | 0x0 | 0x1);

  /* set dev cfg2 reg to defaults and enable */
  i2c_write (devc, addr, 0x4, (0 << 1) | 0x0);

  /* set ratio0 MSB to 0 */
  i2c_write (devc, addr, 0x06, 0x00); 
  /* set ratio0 MSB+8 to 0x10 */
  i2c_write (devc, addr, 0x07, 0x10);
  /* set ratio0 LSB+15 to 0 */
  i2c_write (devc, addr, 0x08, 0x00);
  /* set ratio0 LSB+7 to 0 */
  i2c_write (devc, addr, 0x09, 0x00);

  /* Func reg1 to ref clock div 1 */
  i2c_write (devc, addr, 0x16, 0x10);

  /* set func reg2 to 0 */
  i2c_write (devc, addr, 0x17, 0);

  /* set global cfg to run */
  i2c_write (devc, addr, 0x05, 0x1);
}


static unsigned int
mix_scale (int vol, int bits)
{
  vol = mix_cvt[vol];
  return (vol * ((1 << bits) - 1) / 100);
}


static void
cmi8788_generic_set_play_volume (cmi8788_devc *devc, int codec_id, int left, int right)

{
  spi_write (devc, codec_id, AK4396_LchATTCtl | 0x20, mix_scale(left, 8));
  spi_write (devc, codec_id, AK4396_RchATTCtl | 0x20, mix_scale(right, 8));
}

static void
xonar_d1_set_play_volume(cmi8788_devc * devc, int codec_id, int left, int right)
{
  switch(codec_id)
  {
    case 0:
      i2c_write(devc, XONAR_DX_FRONTDAC, CS4398_VOLA, CS4398_VOL(left));
      i2c_write(devc, XONAR_DX_FRONTDAC, CS4398_VOLB, CS4398_VOL(right));
      break;
    case 1:
      i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_1, CS4362A_VOL(left));
      i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_1, CS4362A_VOL(right));
      break;
    case 2:
      i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_2, CS4362A_VOL(left));
      i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_2, CS4362A_VOL(right));
      break;
    case 3:
      i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_3, CS4362A_VOL(left));
      i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_3, CS4362A_VOL(right));
      break;
  }
}

static void
xonar_d2_set_play_volume(cmi8788_devc *devc, int codec_id, int left, int right)
{
  spi_write (devc, xd2_codec_map[codec_id], 16, mix_scale(left,8));
  spi_write (devc, xd2_codec_map[codec_id], 17, mix_scale(right,8));
}

static void
xonar_stx_set_play_volume(cmi8788_devc * devc, int codec_id, int left, int right)
{
  if (codec_id == 0)
  {
      i2c_write(devc, XONAR_STX_FRONTDAC, 16, mix_scale(left,8));
      i2c_write(devc, XONAR_STX_FRONTDAC, 17, mix_scale(right,8));
  }
}

static void
xonar_st_set_play_volume(cmi8788_devc * devc, int codec_id, int left, int right)
{
  if (codec_id == 0)
  {
      i2c_write(devc, XONAR_ST_FRONTDAC, 16, mix_scale(left,8));
      i2c_write(devc, XONAR_ST_FRONTDAC, 17, mix_scale(right,8));
  }
}

static void
xonar_ds_set_play_volume(cmi8788_devc * devc, int codec_id, int left, int right)
{
  switch (codec_id)
   {
    	case 0:      /* front */
		spi_write (devc, XONAR_DS_FRONTDAC, 0, mix_scale(left,7)|0x80);
		spi_write (devc, XONAR_DS_FRONTDAC, 1, mix_scale(right,7)|0x180);
		spi_write (devc, XONAR_DS_FRONTDAC, 3, mix_scale(left,7)|0x80);
		spi_write (devc, XONAR_DS_FRONTDAC, 4, mix_scale(right,7)|0x180);
                break;

	case 1:      /* side */
                spi_write (devc, XONAR_DS_SURRDAC, 0, mix_scale(left,7)|0x80);
                spi_write (devc, XONAR_DS_SURRDAC, 1, mix_scale(right,7)|0x180);
                break;
	case 2:      /* rear */
                spi_write (devc, XONAR_DS_SURRDAC, 4, mix_scale(left,7)|0x80);
                spi_write (devc, XONAR_DS_SURRDAC, 5, mix_scale(right,7)|0x180);
                break;
	case 3:      /* center */
                spi_write (devc, XONAR_DS_SURRDAC, 6, mix_scale(left,7)|0x80);
                spi_write (devc, XONAR_DS_SURRDAC, 7, mix_scale(right,7)|0x180);
                break;
   }
}

static int
cmi8788_set_rec_volume (cmi8788_devc * devc, int value)
{
  unsigned char left, right;

  left = value & 0xff;
  right = (value >> 8) & 0xff;

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

  devc->recvol = left | (right << 8);

  spi_write (devc, XONAR_DS_FRONTDAC, 0xe, mix_scale(left,8));
  spi_write (devc, XONAR_DS_FRONTDAC, 0xf, mix_scale(right,8));

  return devc->recvol;
}



static int
cmi8788_set_play_volume (cmi8788_devc * devc, int codec_id, int value)
{
  int left, right;

  left = (value & 0x00FF);
  right = (value & 0xFF00) >> 8;

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

  devc->playvol[codec_id] = left | (right<<8);

  switch(devc->model)
  {
    case SUBID_XONAR_D1:
    case SUBID_XONAR_DX:
      xonar_d1_set_play_volume(devc, codec_id, left, right);
      break;
    case SUBID_XONAR_D2:
    case SUBID_XONAR_D2X:
      xonar_d2_set_play_volume(devc, codec_id, left, right);
      break;
    case SUBID_XONAR_STX:
      xonar_stx_set_play_volume(devc, codec_id, left, right);
      break;
    case SUBID_XONAR_ST:
      xonar_st_set_play_volume(devc, codec_id, left, right);
      break;
    case SUBID_XONAR_DS:
      xonar_ds_set_play_volume(devc, codec_id, left, right);
      break;
    default:
      cmi8788_generic_set_play_volume (devc, codec_id, left, right);
      break;
  }
  return devc->playvol[codec_id];
}

static int
cmi8788intr (oss_device_t * osdev)
{
  cmi8788_devc *devc = (cmi8788_devc *) osdev->devc;
  unsigned int intstat;
  int i;
  int serviced = 0;


  intstat = INW (devc->osdev, IRQ_STAT);
  if (intstat != 0)
    serviced = 1;
  else
    return 0;

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

      /* Handle Playback Ints */
      if ((intstat & portc->play_irq_mask)
	  && (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	{

	  /* Acknowledge the interrupt by disabling and enabling the irq */
	  OUTW (devc->osdev,
		INW (devc->osdev, IRQ_MASK) & ~portc->play_irq_mask,
		IRQ_MASK);
	  OUTW (devc->osdev,
		INW (devc->osdev, IRQ_MASK) | portc->play_irq_mask, IRQ_MASK);

	  /* process buffer */
	  oss_audio_outputintr (portc->audiodev, 0);
	}

      /* Handle Record Ints */
      if ((intstat & portc->rec_irq_mask)
	  && (portc->trigger_bits & PCM_ENABLE_INPUT))
	{
	  /* disable the interrupt first */
	  OUTW (devc->osdev,
		INW (devc->osdev, IRQ_MASK) & ~portc->rec_irq_mask, IRQ_MASK);
	  /* enable the interrupt mask */
	  OUTW (devc->osdev,
		INW (devc->osdev, IRQ_MASK) | portc->rec_irq_mask, IRQ_MASK);
	  oss_audio_inputintr (portc->audiodev, 0);
	}

    }

  /* MPU interrupt */
  if (intstat & 0x1000)
    {
      cmi8788uartintr (devc);
    }

  return serviced;
}

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

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

  if (arg > portc->max_rate)
    arg = portc->max_rate;
  if (arg < portc->min_rate)
    arg = portc->min_rate;

  portc->speed = arg;
  return portc->speed;
}

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

  if (audio_engines[dev]->flags & ADEV_STEREOONLY)
    arg = 2;

  if (arg == 1)
    arg = 2;

  if (arg>8)
     arg=8;
  if ((arg != 2) && (arg != 4) && (arg != 6) && (arg != 8))
    return portc->channels;
  portc->channels = arg;

  return portc->channels;
}

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

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

  if (audio_engines[dev]->flags & ADEV_16BITONLY)
    arg = AFMT_S16_LE;

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

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

static void cmi8788_audio_trigger (int dev, int state);

static void
cmi8788_audio_reset (int dev)
{
  cmi8788_audio_trigger (dev, 0);
}

static void
cmi8788_audio_reset_input (int dev)
{
  cmi8788_portc *portc = audio_engines[dev]->portc;
  cmi8788_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
cmi8788_audio_reset_output (int dev)
{
  cmi8788_portc *portc = audio_engines[dev]->portc;
  cmi8788_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
cmi8788_audio_open (int dev, int mode, int open_flags)
{
  cmi8788_portc *portc = audio_engines[dev]->portc;
  cmi8788_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;

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

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

  devc->open_mode |= mode;

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

static void
cmi8788_audio_close (int dev, int mode)
{
  cmi8788_portc *portc = audio_engines[dev]->portc;
  cmi8788_devc *devc = audio_engines[dev]->devc;

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

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

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

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

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

static void
cmi8788_audio_trigger (int dev, int state)
{
  cmi8788_portc *portc = audio_engines[dev]->portc;
  cmi8788_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))
	    {
	      /* Enable Interrupt */
	      OUTW (devc->osdev,
		    INW (devc->osdev, IRQ_MASK) | portc->play_irq_mask,
		    IRQ_MASK);
	      /* enable the dma */
	      OUTW (devc->osdev,
		    INW (devc->osdev, DMA_START) | portc->play_dma_start,
		    DMA_START);
	      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;

	      /* disable dma */
	      OUTW (devc->osdev,
		    INW (devc->osdev, DMA_START) & ~portc->play_dma_start,
		    DMA_START);
	      /* Disable Interrupt */
	      OUTW (devc->osdev,
		    INW (devc->osdev, IRQ_MASK) & ~portc->play_irq_mask,
		    IRQ_MASK);
	    }
	}
    }

  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      /* Enable Interrupt */
	      OUTW (devc->osdev,
		    INW (devc->osdev, IRQ_MASK) | portc->rec_irq_mask,
		    IRQ_MASK);

	      /* enable the channel */
	      OUTW (devc->osdev,
		    INW (devc->osdev, DMA_START) | portc->rec_dma_start,
		    DMA_START);
	      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;
	      /* disable channel  */
	      OUTW (devc->osdev,
		    INW (devc->osdev, DMA_START) & ~portc->rec_dma_start,
		    DMA_START);

	      /* Disable Interrupt */
	      OUTW (devc->osdev,
		    INW (devc->osdev, IRQ_MASK) & ~portc->rec_irq_mask,
		    IRQ_MASK);

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

static void
cmi8788_reset_channel (cmi8788_devc * devc, cmi8788_portc * portc,
		       int direction)
{
  if (direction == PCM_ENABLE_OUTPUT)
    {
      /* reset the channel */
      OUTB (devc->osdev,
	    INB (devc->osdev, CHAN_RESET) | portc->play_chan_reset,
	    CHAN_RESET);
      oss_udelay (10);
      OUTB (devc->osdev,
	    INB (devc->osdev, CHAN_RESET) & ~portc->play_chan_reset,
	    CHAN_RESET);
    }

  if (direction == PCM_ENABLE_INPUT)
    {
      /* reset the channel */
      OUTB (devc->osdev,
	    INB (devc->osdev, CHAN_RESET) | portc->rec_chan_reset,
	    CHAN_RESET);
      oss_udelay (10);
      OUTB (devc->osdev,
	    INB (devc->osdev, CHAN_RESET) & ~portc->rec_chan_reset,
	    CHAN_RESET);
    }

}

static int
i2s_calc_rate (int rate)
{
  int i2s_rate;

  switch (rate)
    {
    case 32000:
      i2s_rate = 0;
      break;
    case 44100:
      i2s_rate = 1;
      break;
    case 48000:
      i2s_rate = 2;
      break;
    case 64000:
      i2s_rate = 3;
      break;
    case 88200:
      i2s_rate = 4;
      break;
    case 96000:
      i2s_rate = 5;
      break;
    case 176400:
      i2s_rate = 6;
      break;
    case 192000:
      i2s_rate = 7;
      break;
    default:
      i2s_rate = 2;
      break;
    }

  return i2s_rate;
}

int
i2s_calc_bits (int bits)
{
  int i2s_bits;

  switch (bits)
    {
#if 0
    case AFMT_S24_LE:
      i2s_bits = 0x80;
      break;
#endif
    case AFMT_S32_LE:
      i2s_bits = 0xC0;
      break;
    default:			/* AFMT_S16_LE */
      i2s_bits = 0x00;
      break;
    }

  return i2s_bits;
}

/*ARGSUSED*/
static int
cmi8788_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  cmi8788_devc *devc = audio_engines[dev]->devc;
  cmi8788_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_in;
  oss_native_word flags;
  int channels, bits, i2s_bits, i2s_rate;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  switch (portc->adc_type)
    {
    case ADEV_I2SADC1:
      {
	portc->rec_dma_start = 0x1;
	portc->rec_irq_mask = 0x1;
	portc->rec_chan_reset = 0x1;
	cmi8788_reset_channel (devc, portc, PCM_ENABLE_INPUT);

	OUTL (devc->osdev, dmap->dmabuf_phys, RECA_ADDR);
	OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, RECA_SIZE);
	OUTW (devc->osdev, dmap->fragment_size / 4 - 1, RECA_FRAG);

	switch (portc->channels)
	  {
	  case 4:
	    channels = 0x1;
	    break;
	  case 6:
	    channels = 0x2;
	    break;
	  case 8:
	    channels = 0x4;
	    break;
	  default:
	    channels = 0x0;	/* Stereo */
	    break;
	  }

	OUTB (devc->osdev, (INB (devc->osdev, REC_MODE) & ~0x3) | channels,
	      REC_MODE);

	switch (portc->bits)
	  {
#if 0
	    /* The 24 bit format supported by cmi8788 is not AFMT_S24_LE */
	  case AFMT_S24_LE:
	    bits = 0x1;
	    break;
#endif
	  case AFMT_S32_LE:
	    bits = 0x2;
	    break;
	  default:		/* AFMT_S16_LE */
	    bits = 0x0;
	    break;
	  }
	OUTB (devc->osdev, (INB (devc->osdev, REC_FORMAT) & ~0x3) | bits,
	      REC_FORMAT);

	/* set up the i2s bits as well */
	i2s_bits = i2s_calc_bits (portc->bits);
	OUTB (devc->osdev,
	      (INB (devc->osdev, I2S_ADC1_FORMAT) & ~0xC0) | i2s_bits,
	      I2S_ADC1_FORMAT);

	/* setup the i2s speed */
	i2s_rate = i2s_calc_rate (portc->speed);
	OUTB (devc->osdev,
	      (INB (devc->osdev, I2S_ADC1_FORMAT) & ~0x7) | i2s_rate,
	      I2S_ADC1_FORMAT);

	break;
      }

    case ADEV_I2SADC2:
      {
	portc->rec_dma_start = 0x2;
	portc->rec_irq_mask = 0x2;
	portc->rec_chan_reset = 0x2;
	cmi8788_reset_channel (devc, portc, PCM_ENABLE_INPUT);

	OUTL (devc->osdev, dmap->dmabuf_phys, RECB_ADDR);
	OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, RECB_SIZE);
	OUTW (devc->osdev, dmap->fragment_size / 4 - 1, RECB_FRAG);

        switch (portc->bits)
          {
#if 0
          case AFMT_S24_LE:
            bits = 0x04;
            break;
#endif
          case AFMT_S32_LE:
            bits = 0x08;
            break;
          default:              /*  AFMT_S16_LE */
            bits = 0x0;
            break;
          }

        OUTB (devc->osdev, (INB (devc->osdev, REC_FORMAT) & ~0x0C) | bits,
              REC_FORMAT);

        /* setup i2s bits */
        i2s_bits = i2s_calc_bits (portc->bits);
        OUTB (devc->osdev,
              (INB (devc->osdev, I2S_ADC2_FORMAT) & ~0xC0) | i2s_bits,
              I2S_ADC2_FORMAT);

        /* setup speed */
        i2s_rate = i2s_calc_rate (portc->speed);
        OUTB (devc->osdev,
              (INB (devc->osdev, I2S_ADC2_FORMAT) & ~0x7) | i2s_rate,
              I2S_ADC2_FORMAT);

	break;
      }

    case ADEV_I2SADC3:
      {
	portc->rec_dma_start = 0x4;
	portc->rec_irq_mask = 0x4;
	portc->rec_chan_reset = 0x4;
	cmi8788_reset_channel (devc, portc, PCM_ENABLE_INPUT);

	OUTL (devc->osdev, dmap->dmabuf_phys, RECC_ADDR);
	OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, RECC_SIZE);
	OUTW (devc->osdev, dmap->fragment_size / 4 - 1, RECC_FRAG);

	switch (portc->bits)
	  {
#if 0
	  case AFMT_S24_LE:
	    bits = 0x10;
	    break;
#endif
	  case AFMT_S32_LE:
	    bits = 0x20;
	    break;
	  default:		/*  AFMT_S16_LE */
	    bits = 0x0;
	    break;
	  }

	OUTB (devc->osdev, (INB (devc->osdev, REC_FORMAT) & ~0x30) | bits,
	      REC_FORMAT);

	/* setup i2s bits */
	i2s_bits = i2s_calc_bits (portc->bits);
	OUTB (devc->osdev,
	      (INB (devc->osdev, I2S_ADC3_FORMAT) & ~0xC0) | i2s_bits,
	      I2S_ADC3_FORMAT);

	/* setup speed */
	i2s_rate = i2s_calc_rate (portc->speed);
	OUTB (devc->osdev,
	      (INB (devc->osdev, I2S_ADC3_FORMAT) & ~0x7) | i2s_rate,
	      I2S_ADC3_FORMAT);
	break;
      }
    }
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;

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

/*ARGSUSED*/
static int
cmi8788_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  cmi8788_devc *devc = audio_engines[dev]->devc;
  cmi8788_portc *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_out;
  oss_native_word flags;
  int i2s_rate, rate, spdif_rate, bits = 0, i2s_bits, channels = 0;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  switch (portc->dac_type)
    {
    case ADEV_MULTICH:
      {
	portc->play_dma_start = 0x10;
	portc->play_irq_mask = 0x10;
	portc->play_chan_reset = 0x10;
	cmi8788_reset_channel (devc, portc, PCM_ENABLE_OUTPUT);

	OUTL (devc->osdev, dmap->dmabuf_phys, MULTICH_ADDR);
	OUTL (devc->osdev, dmap->bytes_in_use / 4 - 1, MULTICH_SIZE);
	OUTL (devc->osdev, dmap->fragment_size / 4 - 1, MULTICH_FRAG);

	switch (portc->channels)
	  {
	  case 2:
	    channels = 0;
	    break;
	  case 4:
	    channels = 1;
	    break;
	  case 6:
	    channels = 2;
	    break;
	  case 8:
	    channels = 3;
	    break;
	  }

	OUTB (devc->osdev,
	      (INB (devc->osdev, MULTICH_MODE) & ~0x3) | channels,
	      MULTICH_MODE);

	/* setup bits per sample */
	switch (portc->bits)
	  {
#if 0
	  case AFMT_S24_LE:
	    bits = 4;
	    break;
#endif
	  case AFMT_S32_LE:
	    bits = 8;
	    break;

	  default:		/* AFMT_S16_LE */
	    bits = 0;
	    break;
	  }

	/* set the format bits in play format register */
	OUTB (devc->osdev, (INB (devc->osdev, PLAY_FORMAT) & ~0xC) | bits,
	      PLAY_FORMAT);

	/* setup the i2s bits in the i2s register */
	i2s_bits = i2s_calc_bits (portc->bits);
	OUTB (devc->osdev,
	      (INB (devc->osdev, I2S_MULTICH_FORMAT) & ~0xC0) | i2s_bits,
	      I2S_MULTICH_FORMAT);

	/* setup speed */
	i2s_rate = i2s_calc_rate (portc->speed);
	OUTB (devc->osdev,
	      (INB (devc->osdev, I2S_MULTICH_FORMAT) & ~0x7) | i2s_rate,
	      I2S_MULTICH_FORMAT);

	break;
      }

    case ADEV_FRONTPANEL:
      {
	portc->play_dma_start = 0x20;
	portc->play_irq_mask = 0x20;
	portc->play_chan_reset = 0x20;
	cmi8788_reset_channel (devc, portc, PCM_ENABLE_OUTPUT);

	OUTL (devc->osdev, dmap->dmabuf_phys, FPOUT_ADDR);
	OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, FPOUT_SIZE);
	OUTW (devc->osdev, dmap->fragment_size / 4 - 1, FPOUT_FRAG);
	ac97_playrate (&devc->fp_ac97devc, portc->speed);
	ac97_spdif_setup (devc->fp_mixer_dev, portc->speed, portc->bits);

	break;
      }

    case ADEV_SPDIF:
      {
	portc->play_dma_start = 0x08;
	portc->play_irq_mask = 0x08;
	portc->play_chan_reset = 0x08;
	cmi8788_reset_channel (devc, portc, PCM_ENABLE_OUTPUT);

	/* STOP SPDIF Out */
	OUTL (devc->osdev, (INL (devc->osdev, SPDIF_FUNC) & ~0x00000002),
	      SPDIF_FUNC);

	/* setup AC3 for 16bit 48Khz, Non-Audio */
	if (portc->bits == AFMT_AC3)
	  {
	    portc->bits = 16;
	    portc->channels = 2;
	    portc->speed = 48000;

	    /* set the PCM/Data bit to Non-Audio */
	    OUTL (devc->osdev,
		  (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x0002) | 0x0002,
		  SPDIFOUT_CHAN_STAT);
	  }
	else
	  OUTL (devc->osdev,
		INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x0002,
		SPDIFOUT_CHAN_STAT);

	OUTL (devc->osdev, dmap->dmabuf_phys, SPDIF_ADDR);
	OUTW (devc->osdev, dmap->bytes_in_use / 4 - 1, SPDIF_SIZE);
	OUTW (devc->osdev, dmap->fragment_size / 4 - 1, SPDIF_FRAG);

	/* setup number of bits/sample */
	switch (portc->bits)
	  {
	  case 16:
	    bits = 0;
	    break;
	  case 24:
	    bits = 1;
	    break;
	  case 32:
	    bits = 2;
	    break;
	  }

	OUTB (devc->osdev, (INB (devc->osdev, PLAY_FORMAT) & ~0x3) | bits,
	      PLAY_FORMAT);

	/* setup sampling rate */
	switch (portc->speed)
	  {
	  case 32000:
	    rate = 0;
	    spdif_rate = 0x3;
	    break;
	  case 44100:
	    rate = 1;
	    spdif_rate = 0x0;
	    break;
	  case 48000:
	    rate = 2;
	    spdif_rate = 0x2;
	    break;
	  case 64000:
	    rate = 3;
	    spdif_rate = 0xb;
	    break;
	  case 88200:
	    rate = 4;
	    spdif_rate = 0x8;
	    break;
	  case 96000:
	    rate = 5;
	    spdif_rate = 0xa;
	    break;
	  case 176400:
	    rate = 6;
	    spdif_rate = 0xc;
	    break;
	  case 192000:
	    rate = 7;
	    spdif_rate = 0xe;
	    break;
	  default:
	    rate = 2;		/* 48000 */
	    spdif_rate = 0x2;
	    break;
	  }

	OUTL (devc->osdev,
	      (INL (devc->osdev, SPDIF_FUNC) & ~0x0f000000) | rate << 24,
	      SPDIF_FUNC);

	/* also program the Channel status */
	OUTL (devc->osdev,
	      (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF000) | spdif_rate
	      << 12, SPDIFOUT_CHAN_STAT);

	/* Enable SPDIF Out */
	OUTL (devc->osdev,
	      (INL (devc->osdev, SPDIF_FUNC) & ~0x00000002) | 0x2,
	      SPDIF_FUNC);

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

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

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_INPUT)
    {
      switch (portc->adc_type)
	{
	case ADEV_I2SADC1:
	  ptr = INL (devc->osdev, RECA_ADDR);
	  break;
	case ADEV_I2SADC2:
	  ptr = INL (devc->osdev, RECB_ADDR);
	  break;
	case ADEV_I2SADC3:
	  ptr = INL (devc->osdev, RECC_ADDR);
	  break;
	}
    }

  if (direction == PCM_ENABLE_OUTPUT)
    {
      switch (portc->dac_type)
	{
	case ADEV_MULTICH:
	  ptr = INL (devc->osdev, MULTICH_ADDR);
	  break;
	case ADEV_FRONTPANEL:
	  ptr = INL (devc->osdev, FPOUT_ADDR);
	  break;
	case ADEV_SPDIF:
	  ptr = INL (devc->osdev, SPDIF_ADDR);
	  break;
	}
    }

  ptr -= dmap->dmabuf_phys;
  ptr %= dmap->bytes_in_use;
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr;
}


static audiodrv_t cmi8788_audio_driver = {
  cmi8788_audio_open,
  cmi8788_audio_close,
  cmi8788_audio_output_block,
  cmi8788_audio_start_input,
  cmi8788_audio_ioctl,
  cmi8788_audio_prepare_for_input,
  cmi8788_audio_prepare_for_output,
  cmi8788_audio_reset,
  NULL,
  NULL,
  cmi8788_audio_reset_input,
  cmi8788_audio_reset_output,
  cmi8788_audio_trigger,
  cmi8788_audio_set_rate,
  cmi8788_audio_set_format,
  cmi8788_audio_set_channels,
  NULL,
  NULL,
  NULL,				/* cmi8788_check_input, */
  NULL,				/* cmi8788_check_output, */
  NULL,				/* cmi8788_alloc_buffer */
  NULL,				/* cmi8788_free_buffer */
  NULL,
  NULL,
  cmi8788_get_buffer_pointer
};

#define input_avail(devc) (!(cmi8788uart_status(devc)&INPUT_AVAIL))
#define output_ready(devc)      (!(cmi8788uart_status(devc)&OUTPUT_READY))

static __inline__ int
cmi8788uart_status (cmi8788_devc * devc)
{
  return INB (devc->osdev, MPU401_COMMAND);
}

static void
cmi8788uart_cmd (cmi8788_devc * devc, unsigned char cmd)
{
  OUTB (devc->osdev, cmd, MPU401_COMMAND);
}

static __inline__ int
cmi8788uart_read (cmi8788_devc * devc)
{
  return INB (devc->osdev, MPU401_DATA);
}

static __inline__ void
cmi8788uart_write (cmi8788_devc * devc, unsigned char byte)
{
  OUTB (devc->osdev, byte, MPU401_DATA);
}

#define OUTPUT_READY    0x40
#define INPUT_AVAIL     0x80
#define MPU_ACK         0xFE
#define MPU_RESET       0xFF
#define UART_MODE_ON    0x3F


static void
cmi8788uart_input_loop (cmi8788_devc * devc)
{
  while (input_avail (devc))
    {
      unsigned char c = cmi8788uart_read (devc);

      if (c == MPU_ACK)
	devc->input_byte = c;
      else if (devc->midi_opened & OPEN_READ && devc->midi_input_intr)
	devc->midi_input_intr (devc->midi_dev, c);
    }
}

static void
cmi8788uartintr (cmi8788_devc * devc)
{
  cmi8788uart_input_loop (devc);
}

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

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

  while (input_avail (devc))
    cmi8788uart_read (devc);

  devc->midi_input_intr = inputbyte;
  devc->midi_opened = mode;
  enter_uart_mode (devc);
  devc->midi_disabled = 0;

  return 0;
}

/*ARGSUSED*/
static void
cmi8788uart_close (int dev, int mode)
{
  cmi8788_devc *devc = (cmi8788_devc *) midi_devs[dev]->devc;
  reset_cmi8788uart (devc);
  oss_udelay (10);
  enter_uart_mode (devc);
  reset_cmi8788uart (devc);
  devc->midi_opened = 0;
}


static int
cmi8788uart_out (int dev, unsigned char midi_byte)
{
  int timeout;
  cmi8788_devc *devc = (cmi8788_devc *) midi_devs[dev]->devc;
  oss_native_word flags;

  /*
   * Test for input since pending input seems to block the output.
   */

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (input_avail (devc))
    cmi8788uart_input_loop (devc);

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  /*
   * Sometimes it takes about 130000 loops before the output becomes ready
   * (After reset). Normally it takes just about 10 loops.
   */

  for (timeout = 130000; timeout > 0 && !output_ready (devc); timeout--);

  if (!output_ready (devc))
    {
      cmn_err (CE_WARN, "UART timeout - Device not responding\n");
      devc->midi_disabled = 1;
      reset_cmi8788uart (devc);
      enter_uart_mode (devc);
      return 1;
    }

  cmi8788uart_write (devc, midi_byte);
  return 1;
}

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

static midi_driver_t cmi8788_midi_driver = {
  cmi8788uart_open,
  cmi8788uart_close,
  cmi8788uart_ioctl,
  cmi8788uart_out,
};

static void
enter_uart_mode (cmi8788_devc * devc)
{
  int ok, timeout;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);

  devc->input_byte = 0;
  cmi8788uart_cmd (devc, UART_MODE_ON);

  ok = 0;
  for (timeout = 50000; timeout > 0 && !ok; timeout--)
    if (devc->input_byte == MPU_ACK)
      ok = 1;
    else if (input_avail (devc))
      if (cmi8788uart_read (devc) == MPU_ACK)
	ok = 1;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}


void
attach_cmi8788uart (cmi8788_devc * devc)
{
  enter_uart_mode (devc);
  devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "CMI8788", "CMI8788 UART", &cmi8788_midi_driver, sizeof (midi_driver_t),
					0, devc, devc->osdev);
  devc->midi_opened = 0;
}

static int
reset_cmi8788uart (cmi8788_devc * devc)
{
  int ok, timeout, n;

  /*
   * Send the RESET command. Try again if no success at the first time.
   */

  ok = 0;

  for (n = 0; n < 2 && !ok; n++)
    {
      for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);

      devc->input_byte = 0;
      cmi8788uart_cmd (devc, MPU_RESET);

      /*
       * Wait at least 25 msec. This method is not accurate so let's make the
       * loop bit longer. Cannot sleep since this is called during boot.
       */

      for (timeout = 50000; timeout > 0 && !ok; timeout--)
	if (devc->input_byte == MPU_ACK)	/* Interrupt */
	  ok = 1;
	else if (input_avail (devc))
	  if (cmi8788uart_read (devc) == MPU_ACK)
	    ok = 1;

    }



  if (ok)
    cmi8788uart_input_loop (devc);	/*
					 * Flush input before enabling interrupts
					 */

  return ok;
}


int
probe_cmi8788uart (cmi8788_devc * devc)
{
  int ok = 0;
  oss_native_word flags;

  DDB (cmn_err (CE_CONT, "Entered probe_cmi8788uart\n"));

  devc->midi_input_intr = NULL;
  devc->midi_opened = 0;
  devc->input_byte = 0;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  ok = reset_cmi8788uart (devc);
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  if (ok)
    {
      DDB (cmn_err (CE_CONT, "Reset UART401 OK\n"));
    }
  else
    {
      DDB (cmn_err
	   (CE_CONT, "Reset UART401 failed (no hardware present?).\n"));
      DDB (cmn_err
	   (CE_CONT, "mpu401 status %02x\n", cmi8788uart_status (devc)));
    }

  DDB (cmn_err (CE_CONT, "cmi8788uart detected OK\n"));
  return ok;
}

void
unload_cmi8788uart (cmi8788_devc * devc)
{
  reset_cmi8788uart (devc);
}


static void
attach_mpu (cmi8788_devc * devc)
{
  devc->mpu_attached = 1;
  attach_cmi8788uart (devc);
}

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

  if (((cmd >> 8) & 0xff) == 'M')
    {
      int val;

      if (IOC_IS_OUTPUT (cmd))
	switch (cmd & 0xff)
	  {
	  case SOUND_MIXER_RECSRC:
	    return *arg = 0;
	    break;

	  case SOUND_MIXER_PCM:
	    val = *arg;
	    return *arg = cmi8788_set_play_volume (devc, 0, val);
	    break;

	  case SOUND_MIXER_REARVOL:
	    val = *arg;
	    return *arg = cmi8788_set_play_volume (devc, 1, val);
	    break;

	  case SOUND_MIXER_CENTERVOL:
	    val = *arg;
	    return *arg = cmi8788_set_play_volume (devc, 2, val);
	    break;

	  case SOUND_MIXER_SIDEVOL:
	    val = *arg;
	    return *arg = cmi8788_set_play_volume (devc, 3, val);
	    break;

	  case SOUND_MIXER_RECLEV:
	    val = *arg;
	    return *arg = cmi8788_set_rec_volume (devc, val);
	    break;

	  default:
	    val = *arg;
	    return *arg = 0;
	    break;
	  }
      else
	switch (cmd & 0xff)	/* Return Parameter */
	  {
	  case SOUND_MIXER_RECSRC:
	    return *arg = 0;
	    break;

	  case SOUND_MIXER_DEVMASK:
            if ((devc->model == SUBID_XONAR_STX) || (devc->model == SUBID_XONAR_ST))
                *arg = SOUND_MASK_PCM;
            else
	    	*arg = SOUND_MASK_PCM | SOUND_MASK_REARVOL | 
			SOUND_MASK_CENTERVOL | SOUND_MASK_SIDEVOL;

	    if (devc->model == SUBID_XONAR_DS)
		*arg |= SOUND_MASK_RECLEV;

	    return *arg;
	    break;

	  case SOUND_MIXER_STEREODEVS:
            if ((devc->model == SUBID_XONAR_STX) || (devc->model == SUBID_XONAR_ST))
               *arg = SOUND_MASK_PCM;
            else
	       *arg = SOUND_MASK_PCM | SOUND_MASK_REARVOL | 
			SOUND_MASK_CENTERVOL | SOUND_MASK_SIDEVOL;

	    if (devc->model == SUBID_XONAR_DS)
		*arg |= SOUND_MASK_RECLEV;

	    return *arg;
	    break;

	  case SOUND_MIXER_CAPS:
	    return *arg = SOUND_CAP_EXCL_INPUT;
	    break;

	  case SOUND_MIXER_PCM:
	    return *arg = devc->playvol[0];
	    break;

	  case SOUND_MIXER_REARVOL:
	    return *arg = devc->playvol[1];
	    break;

	  case SOUND_MIXER_CENTERVOL:
	    return *arg = devc->playvol[2];
	    break;

	  case SOUND_MIXER_SIDEVOL:
	    return *arg = devc->playvol[3];
	    break;
	
	  case SOUND_MIXER_RECLEV:
	    return *arg = devc->recvol;
	    break;

	  default:
	    return *arg = 0;
	    break;
	  }
    }
  else
    return *arg = 0;

}

static mixer_driver_t cmi8788_mixer_driver = {
  cmi8788_mixer_ioctl
};

/********************Record/Play ROUTING Control *************************/

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

  if (cmd == SNDCTL_MIX_READ)
    {
      value = 0;
      switch (ctrl)
	{
	case 0:		/* Record Monitor */
	  value = (INB (devc->osdev, REC_MONITOR) & 0x1) ? 1 : 0;
	  break;
	case 1:		/* SPDIFIN Monitor */
	  value = (INB (devc->osdev, REC_MONITOR) & 0x10) ? 1 : 0;
	  break;
	case 2:		/* Record source select */
	  switch (devc->model)
		{
		  case SUBID_XONAR_DS:		
	  		value = (INW (devc->osdev, GPIO_DATA) & 0x40) ? 1 : 0;
			break;
		  case SUBID_XONAR_D1:
		  case SUBID_XONAR_DX:
		  case SUBID_XONAR_STX:
		  case SUBID_XONAR_ST:
			value = (INW (devc->osdev, GPIO_DATA) & 0x100) ? 1 : 0;
			break;
		  default: 
	  		value = (ac97_read (devc, 0x72) & 0x1) ? 1 : 0;
			break;
	        }
	  break;
	case 3:		/* Speaker Spread - check bit15 to see if it's set */
	  value = (INW (devc->osdev, PLAY_ROUTING) & 0x8000) ? 0 : 1;
	  break;
	case 4:		/* SPDIF IN->OUT Loopback */
	  value = (INW (devc->osdev, SPDIF_FUNC) & 0x4) ? 1 : 0;
	  break;
	case 5:
	  value = (INW (devc->osdev, GPIO_DATA) & 0x80) ? 1 : 0;
	  break;
	case 6:
	  if (!(INW (devc->osdev, GPIO_DATA) & 0x80))
		value = 0;
	  else if (INW (devc->osdev, GPIO_DATA) & 0x02)
		value = 1;
	  else
		value = 2;
	  break;
	case 7:
	  value = devc->mute;
	  break;
	
	default:
	  return OSS_EINVAL;
	  break;
	}

      return value;
    }


  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (ctrl)
	{
	case 0:		/* Record Monitor */
	  if (value)
	    OUTB (devc->osdev, INB (devc->osdev, REC_MONITOR) | 0xF,
		  REC_MONITOR);
	  else
	    OUTB (devc->osdev, INB (devc->osdev, REC_MONITOR) & ~0xF,
		  REC_MONITOR);
	  break;

	case 1:		/* SPDIFIN Monitor */
	  if (value)
	    OUTB (devc->osdev, INB (devc->osdev, REC_MONITOR) | 0x10,
		  REC_MONITOR);
	  else
	    OUTB (devc->osdev, INB (devc->osdev, REC_MONITOR) & ~0x10,
		  REC_MONITOR);
	  break;

	case 2:
	  if (value)
          {
	     switch (devc->model)
		{
		case SUBID_XONAR_DS:
			OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x40, GPIO_DATA);
			break;
		case SUBID_XONAR_D1:
		case SUBID_XONAR_DX:
		case SUBID_XONAR_STX:
		case SUBID_XONAR_ST:
			OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x100, GPIO_DATA);
			break;
		}
	     ac97_write(devc, 0x72, ac97_read(devc, 0x72) | 0x1);
	  }
	  else
	  {
             switch (devc->model)
                {
                case SUBID_XONAR_DS:
                        OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) & ~0x40, GPIO_DATA);
                        break;
                case SUBID_XONAR_D1:
                case SUBID_XONAR_DX:
		case SUBID_XONAR_STX:
		case SUBID_XONAR_ST:
                        OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) & ~0x100, GPIO_DATA);
                        break;
                }

	     ac97_write(devc, 0x72, ac97_read(devc, 0x72) & ~0x1);
	  }
	  break;

	case 3:		/* Speaker Spread (clone front to all channels) */
	  if (value)
	    OUTW (devc->osdev, INW (devc->osdev, PLAY_ROUTING) & 0x00FF,
		  PLAY_ROUTING);
	  else
	    OUTW (devc->osdev,
		  (INW (devc->osdev, PLAY_ROUTING) & 0x00FF) | 0xE400,
		  PLAY_ROUTING);
	  break;

	case 4:		/* SPDIF IN->OUT Loopback */
	  if (value)
	    OUTW (devc->osdev, INW (devc->osdev, SPDIF_FUNC) | 0x4,
		  SPDIF_FUNC);
	  else
	    OUTW (devc->osdev, INW (devc->osdev, SPDIF_FUNC) & ~0x4,
		  SPDIF_FUNC);
	  break;

	case 5:
          if (value)
		OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x80, GPIO_DATA) ;
	  else
		OUTW(devc->osdev, (INW(devc->osdev, GPIO_DATA) & ~0x80), GPIO_DATA);
          break;

	case 6:
          switch (value)
	  {
		/* speaker (line) output */
		case 0: OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) & ~(0x80|0x02), GPIO_DATA); 	
			 break;
		/* rear headphone output */
		case 1: OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | (0x80|0x02), GPIO_DATA);
			 break;
		case 2: OUTW (devc->osdev, (INW(devc->osdev, GPIO_DATA) | 0x80) & ~0x02, GPIO_DATA);
			break;

	  }
          break;

	case 7:
	  /* muting for Xonar ST/STX on PCM1796 */
          if (value)
	    {
		i2c_write (devc, XONAR_ST_FRONTDAC, 18, 0x01|0x30|0x80);
		devc->mute=1;
	    }
	  else
	    {
		i2c_write (devc, XONAR_ST_FRONTDAC, 18, 0x00|0x30|0x80);
		devc->mute=0;
	    }
          break;

	default:
	  return OSS_EINVAL;
	  break;
	}
      return (value);
    }
  return OSS_EINVAL;
}


/********************SPDIFOUT Control *************************/
int
spdifout_ctl (int dev, int ctrl, unsigned int cmd, int value)
{
  int tmp = 0;
  cmi8788_devc *devc = mixer_devs[dev]->devc;

  if (cmd == SNDCTL_MIX_READ)
    {
      value = 0;
      switch (ctrl)
	{
	case SPDIFOUT_ENABLE:	/* SPDIF OUT */
	  value = (INL (devc->osdev, SPDIF_FUNC) & 0x2) ? 1 : 0;
	  break;

	case SPDIFOUT_PRO:	/* Consumer/PRO */
	  value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x1) ? 1 : 0;
	  break;

	case SPDIFOUT_AUDIO:	/* PCM/AC3 */
	  value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x2) ? 1 : 0;
	  break;

	case SPDIFOUT_COPY:	/* Copy Prot */
	  value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x4) ? 1 : 0;
	  break;

	case SPDIFOUT_PREEMPH:	/* Pre emphasis */
	  value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x8) ? 1 : 0;
	  break;

	case SPDIFOUT_RATE:	/* Sampling Rate */
	  tmp = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0xf000);
	  switch (tmp)
	    {
	    case 0x0000:	/* 44100 */
	      value = 0;
	      break;
	    case 0x2000:	/* 48000 */
	      value = 1;
	      break;
	    case 0x3000:	/* 32000 */
	      value = 2;
	      break;
	    case 0x8000:	/* 88200 */
	      value = 3;
	      break;
	    case 0xA000:	/* 96000 */
	      value = 4;
	      break;
	    case 0xB000:	/* 64000 */
	      value = 5;
	      break;
	    case 0xC000:	/* 176400 */
	      value = 6;
	      break;
	    case 0xE000:	/* 192000 */
	      value = 7;
	      break;
	    default:
	      cmn_err (CE_WARN, "unsupported SPDIF F/S rate\n");
	      break;
	    }
	  break;

	case SPDIFOUT_VBIT:	/* V Bit */
	  value = (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & 0x10000) ? 1 : 0;
	  break;

	case SPDIFOUT_ADC:	/* Analog In to SPDIF Out */
	  value = (INW (devc->osdev, PLAY_ROUTING) & 0x80) ? 1 : 0;
	  break;

	default:
	  break;
	}

      return value;
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (ctrl)
	{
	case SPDIFOUT_ENABLE:	/* Enable SPDIF OUT */
	  if (value)
	    OUTL (devc->osdev, INL (devc->osdev, SPDIF_FUNC) | 0x2,
		  SPDIF_FUNC);
	  else
	    OUTL (devc->osdev, INL (devc->osdev, SPDIF_FUNC) & ~0x2,
		  SPDIF_FUNC);
	  break;

	case SPDIFOUT_PRO:	/* consumer/pro audio */
	  if (value)
	    OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x1,
		  SPDIFOUT_CHAN_STAT);
	  else
	    OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x1,
		  SPDIFOUT_CHAN_STAT);
	  break;

	case SPDIFOUT_AUDIO:	/* PCM/AC3 */
	  if (value)
	    OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x2,
		  SPDIFOUT_CHAN_STAT);
	  else
	    OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x2,
		  SPDIFOUT_CHAN_STAT);
	  break;

	case SPDIFOUT_COPY:	/* copy prot */
	  if (value)
	    OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x4,
		  SPDIFOUT_CHAN_STAT);
	  else
	    OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x4,
		  SPDIFOUT_CHAN_STAT);
	  break;

	case SPDIFOUT_PREEMPH:	/* preemphasis */
	  if (value)
	    OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x8,
		  SPDIFOUT_CHAN_STAT);
	  else
	    OUTL (devc->osdev, INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x8,
		  SPDIFOUT_CHAN_STAT);
	  break;

	case SPDIFOUT_RATE:	/* Frequency */
	  switch (value)
	    {
	    case 0:		/* 44100 */
	      OUTL (devc->osdev,
		    (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) | 0x0,
		    SPDIFOUT_CHAN_STAT);
	      break;
	    case 1:		/* 48000 */
	      OUTL (devc->osdev,
		    (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
		    0x2000, SPDIFOUT_CHAN_STAT);
	      break;
	    case 2:		/* 32000 */
	      OUTL (devc->osdev,
		    (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
		    0x3000, SPDIFOUT_CHAN_STAT);
	      break;
	    case 3:		/* 88000 */
	      OUTL (devc->osdev,
		    (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
		    0x8000, SPDIFOUT_CHAN_STAT);
	      break;
	    case 4:		/* 96000 */
	      OUTL (devc->osdev,
		    (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
		    0xA000, SPDIFOUT_CHAN_STAT);
	      break;
	    case 5:		/* 64000 */
	      OUTL (devc->osdev,
		    (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
		    0xB000, SPDIFOUT_CHAN_STAT);
	      break;
	    case 6:		/* 176400 */
	      OUTL (devc->osdev,
		    (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
		    0xC000, SPDIFOUT_CHAN_STAT);
	      break;
	    case 7:		/* 192000 */
	      OUTL (devc->osdev,
		    (INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0xF0000) |
		    0xE000, SPDIFOUT_CHAN_STAT);
	      break;

	    default:
	      break;
	    }
	  break;

	case SPDIFOUT_VBIT:	/* V Bit */
	  if (value)
	    OUTL (devc->osdev,
		  INL (devc->osdev, SPDIFOUT_CHAN_STAT) | 0x10000,
		  SPDIFOUT_CHAN_STAT);
	  else
	    OUTL (devc->osdev,
		  INL (devc->osdev, SPDIFOUT_CHAN_STAT) & ~0x10000,
		  SPDIFOUT_CHAN_STAT);
	  break;

	case SPDIFOUT_ADC:	/* Analog In to SPDIF Out */
	  if (value)
	    OUTW (devc->osdev, INW (devc->osdev, PLAY_ROUTING) | 0xA0,
		  PLAY_ROUTING);
	  else
	    OUTW (devc->osdev, INW (devc->osdev, PLAY_ROUTING) & ~0xE0,
		  PLAY_ROUTING);
	  break;

	default:
	  break;
	}

      return (value);
    }

  return OSS_EINVAL;
}

static int
cmi8788_mix_init (int dev)
{
  int group, parent, ctl;
  cmi8788_devc *devc = mixer_devs[dev]->hw_devc;

  if ((parent = mixer_ext_create_group (dev, 0, "EXT")) < 0)
    return parent;

/* CREATE MONITOR */
  if ((group = mixer_ext_create_group (dev, parent, "MONITOR")) < 0)
    return group;

  if ((ctl =
       mixer_ext_create_control (dev, group, 0, cmi8788_ext,
				 MIXT_ONOFF, "ANALOG", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  if ((ctl =
       mixer_ext_create_control (dev, group, 1, cmi8788_ext,
				 MIXT_ONOFF, "SPDIF", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

 if ((ctl =
	mixer_ext_create_control (dev, group, 2, cmi8788_ext, MIXT_ENUM,
				  "INPUTSRC", 2,
				  MIXF_READABLE | MIXF_WRITEABLE)) < 0)
	return ctl;
    mixer_ext_set_strings (dev, ctl, "Line Mic", 0);

/* Create PLAYBACK ROUTING */
  if ((group = mixer_ext_create_group (dev, parent, "ROUTING")) < 0)
    return group;

  if ((ctl =
       mixer_ext_create_control (dev, group, 3, cmi8788_ext,
				 MIXT_ONOFF, "SPREAD", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  if ((ctl =
       mixer_ext_create_control (dev, group, 4, cmi8788_ext,
				 MIXT_ONOFF, "SPDIF-LOOPBACK", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  if (devc->model == SUBID_XONAR_D2 || devc->model == SUBID_XONAR_D2X)
    if ((ctl = mixer_ext_create_control (dev, group, 5, cmi8788_ext,
				 MIXT_ONOFF, "PCM-LOOPBACK", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
		        return ctl;

  if (devc->model == SUBID_XONAR_STX || devc->model == SUBID_XONAR_ST)

   {
    if ((ctl =
	mixer_ext_create_control (dev, group, 6, cmi8788_ext, MIXT_ENUM,
				  "OUTPUTSRC", 3,
				  MIXF_READABLE | MIXF_WRITEABLE)) < 0)
	return ctl;
    mixer_ext_set_strings (dev, ctl, "Speaker Headphone FrontHeadphone", 0);


    if ((ctl =
       mixer_ext_create_control (dev, group, 7, cmi8788_ext,
				 MIXT_ONOFF, "MUTE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
        return ctl;
   }

/* Create SPDIF OUTPUT */
  if ((group = mixer_ext_create_group (dev, 0, "SPDIF-OUT")) < 0)
    return group;

  if ((ctl =
       mixer_ext_create_control (dev, group, SPDIFOUT_ENABLE,
				 spdifout_ctl, MIXT_ONOFF,
				 "SPDOUT_ENABLE", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  if ((ctl =
       mixer_ext_create_control (dev, group, SPDIFOUT_ADC,
				 spdifout_ctl, MIXT_ONOFF,
				 "SPDOUT_ADC/DAC", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  if ((ctl =
       mixer_ext_create_control (dev, group, SPDIFOUT_PRO,
				 spdifout_ctl, MIXT_ENUM,
				 "SPDOUT_Pro", 2,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;
  mixer_ext_set_strings (dev, ctl, "Consumer Professional", 0);

  if ((ctl =
       mixer_ext_create_control (dev, group, SPDIFOUT_AUDIO,
				 spdifout_ctl, MIXT_ENUM,
				 "SPDOUT_Audio", 2,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;
  mixer_ext_set_strings (dev, ctl, "Audio Data", 0);

  if ((ctl =
       mixer_ext_create_control (dev, group, SPDIFOUT_COPY,
				 spdifout_ctl, MIXT_ONOFF,
				 "SPDOUT_Copy", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  if ((ctl =
       mixer_ext_create_control (dev, group, SPDIFOUT_PREEMPH,
				 spdifout_ctl, MIXT_ONOFF,
				 "SPDOUT_Pre-emph", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  if ((ctl =
       mixer_ext_create_control (dev, group, SPDIFOUT_RATE,
				 spdifout_ctl, MIXT_ENUM,
				 "SPDOUT_Rate", 8,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  mixer_ext_set_strings (dev, ctl,
			 "44.1KHz 48KHz 32KHz 88.2KHz 96KHz 64KHz 176.4KHz 192KHz",
			 0);

  if ((ctl =
       mixer_ext_create_control (dev, group, SPDIFOUT_VBIT,
				 spdifout_ctl, MIXT_ONOFF,
				 "SPDOUT_VBit", 1,
				 MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return ctl;

  return 0;
}


static void 
ac97_hwinit(cmi8788_devc *devc)
{

    /* Gpio #0 programmed as output, set CMI9780 Reg0x70 */
    ac97_write(devc, 0x70, 0x100);

    /* LI2LI,MIC2MIC; let them always on, FOE on, ROE/BKOE/CBOE off */
    ac97_write(devc, 0x62, 0x180F);

    /* change PCBeep path, set Mix2FR on, option for quality issue */
    ac97_write(devc, 0x64, 0x8043);
#if 0
   /* unmute Master Volume */
    ac97_write(devc, 0x02, 0x0);


    /* mute PCBeep, option for quality issues */
    ac97_write(devc, 0x0A, 0x8000);

    /* Record Select Control Register (Index 1Ah) */
    ac97_write(devc, 0x1A, 0x0000);

    /* set Mic Volume Register 0x0Eh umute and enable micboost */
    ac97_write(devc, 0x0E, 0x0848);

    /* set Line in Volume Register 0x10h mute */
    ac97_write(devc, 0x10, 0x8808);

    /* set CD Volume Register 0x12h mute */
    ac97_write(devc, 0x12, 0x8808);

    /* set AUX Volume Register 0x16h max */
    ac97_write(devc, 0x16, 0x0808);

    /* set record gain Register 0x1Ch to max */
    ac97_write(devc, 0x1C, 0x0F0F);
#endif
    ac97_write(devc, 0x71, 0x0001);
}

static int
init_cmi8788 (cmi8788_devc * devc)
{
  unsigned short sVal;
  unsigned short sDac;
  unsigned char bVal;
  int i, first_dev = -1, count;
  int default_vol;
/*
 * Init CMI Controller
 */
  sVal = INW (devc->osdev, CTRL_VERSION);
  if (!(sVal & 0x0008))
    {
      bVal = INB (devc->osdev, MISC_REG);
      bVal |= 0x20;
      OUTB (devc->osdev, bVal, MISC_REG);
    }

  bVal = INB (devc->osdev, FUNCTION);
  bVal |= 0x02; /* Reset codec*/
  OUTB(devc->osdev, bVal, FUNCTION);


  /* I2S to 16bit, see below. */
  sDac = 0x010A; 
 
  /* Non-generic DAC initialization */
  switch(devc->model)
  {
    case SUBID_XONAR_D1:
    case SUBID_XONAR_DX:
    case SUBID_XONAR_D2:
    case SUBID_XONAR_D2X:
    case SUBID_XONAR_STX:
    case SUBID_XONAR_DS:
      /* Must set master clock. */
      sDac |= XONAR_MCLOCK_256;
      break;
    case SUBID_XONAR_ST:
      sDac |= XONAR_MCLOCK_512;
  }

  /* Setup I2S to use 16bit instead of 24Bit */
  OUTW (devc->osdev, sDac, I2S_MULTICH_FORMAT);
  OUTW (devc->osdev, sDac, I2S_ADC1_FORMAT);
  OUTW (devc->osdev, sDac, I2S_ADC2_FORMAT);
  OUTW (devc->osdev, sDac, I2S_ADC3_FORMAT);

  /* setup Routing regs (default vals) */
  OUTW (devc->osdev, 0xE400, PLAY_ROUTING);
  OUTB (devc->osdev, 0x00, REC_ROUTING);
  OUTB (devc->osdev, 0x00, REC_MONITOR);
  OUTB (devc->osdev, 0xE4, MONITOR_ROUTING);


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

  mixer_devs[devc->cmi_mixer_dev]->hw_devc = devc;
  mixer_devs[devc->cmi_mixer_dev]->priority = 10;	/* Possible default mixer candidate */
  mixer_ext_set_init_fn (devc->cmi_mixer_dev, cmi8788_mix_init, 25);
  
  /* Cold reset onboard AC97 */
  OUTW (devc->osdev, 0x1, AC97_CTRL);
  count = 100;
  while ((INW (devc->osdev, AC97_CTRL) & 0x2) && (count--))
    {
      OUTW (devc->osdev, (INW (devc->osdev, AC97_CTRL) & ~0x2) | 0x2,
	    AC97_CTRL);
      oss_udelay (100);
    }
  if (!count)
    cmn_err (CE_WARN, "CMI8788 AC97 not ready\n");


  sVal = INW (devc->osdev, AC97_CTRL);

  devc->ac97_mixer_dev = devc->fp_mixer_dev = -1;

  /* check if there's an onboard AC97 codec (CODEC 0)  and install the mixer */
  if (sVal & 0x10)
    {
      /* disable CODEC0 OUTPUT */
      OUTW (devc->osdev, 0, /* INW (devc->osdev, AC97_OUT_CHAN_CONFIG) & ~0xFF00,*/
	    AC97_OUT_CHAN_CONFIG);

      /* enable CODEC0 INPUT */
      OUTW (devc->osdev, 0, /* INW (devc->osdev, AC97_IN_CHAN_CONFIG) | 0x0300,*/
	    AC97_IN_CHAN_CONFIG);

      devc->ac97_mixer_dev =
	ac97_install (&devc->ac97devc, "AC97 Input Mixer", ac97_read,
		      ac97_write, devc, devc->osdev);
    }

  /* check if there's an front panel AC97 codec (CODEC1) and install the mixer */
  if (sVal & 0x20)
    {
      /* enable CODEC1 OUTPUT */
      OUTW (devc->osdev, INW (devc->osdev, AC97_OUT_CHAN_CONFIG) | 0x0033,
	    AC97_OUT_CHAN_CONFIG);
      /* enable CODEC1 INPUT */
      OUTW (devc->osdev, INW (devc->osdev, AC97_IN_CHAN_CONFIG) | 0x0033,
	    AC97_IN_CHAN_CONFIG);

      devc->fp_mixer_dev =
	ac97_install (&devc->fp_ac97devc, "AC97 Mixer (Front Panel)",
		      fp_ac97_read, fp_ac97_write, devc, devc->osdev);

      if (devc->fp_mixer_dev >= 0)
	{
	  /* enable S/PDIF */
	  devc->fp_ac97devc.spdif_slot = SPDIF_SLOT34;
	  ac97_spdifout_ctl (devc->fp_mixer_dev, SPDIFOUT_ENABLE,
			     SNDCTL_MIX_WRITE, 1);
	}
    }



    switch(devc->model) {
            case SUBID_XONAR_D1:
            case SUBID_XONAR_DX:
                    /*GPIO8 = 0x100 controls mic/line-in */
                    /*GPIO0 = 0x001controls output */
                    /*GPIO2/3 = 0x00C codec output control*/

                    /* setup for i2c communication mode */
                    OUTB(devc->osdev, INB (devc->osdev, FUNCTION) | 0x40, FUNCTION);
                    /* setup GPIO direction */
                    OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x010D, GPIO_CONTROL);
                    /* setup GPIO pins */
                    OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x0101, GPIO_DATA);

                    /* init the front and rear dacs */
                    cs4398_init(devc, XONAR_DX_FRONTDAC);
                    cs4362a_init(devc, XONAR_DX_SURRDAC);
  		    /* initialize the codec 0 */
  		    ac97_hwinit(devc);
                    break;


            case SUBID_XONAR_D2:
            case SUBID_XONAR_D2X:
                    /*GPIO7 = 0x0080 controls mic/line-in */
                    /*GPIO8 = 0x0100 controls output */
                    /*GPIO2/3 = 0x000C codec output control*/

                    /* setup for spi communication mode */
                    OUTB(devc->osdev, (INB (devc->osdev, FUNCTION) & ~0x40) | 0x80, FUNCTION);
                    /* setup the GPIO direction */
                    OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x018C, GPIO_CONTROL);
	            /* setup GPIO Pins */
	            OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x0100, GPIO_DATA);

                    /* for all 4 codecs: unmute, set to 24Bit SPI */
		    for (i = 0; i < 4; ++i) {
			spi_write(devc, xd2_codec_map[i], 16, mix_scale(75,8)); /* left vol*/
			spi_write(devc, xd2_codec_map[i], 17, mix_scale(75,8)); /* right vol */
			spi_write(devc, xd2_codec_map[i], 18, 0x00| 0x30 | 0x80); /* unmute/24LSB/ATLD */
		    }
  		    /* initialize the codec 0 */
  		    ac97_hwinit(devc);
		    break;

 		case SUBID_XONAR_STX:
            	    /*GPIO0 = Antipop control */
                    /*GPIO1 = frontpanel h/p control*/
                    /*GPIO7 = 0x0080 controls analog out*/
                    /*GPIO8 = 0x0100 controls mic/line in*/
                    /*GPIO2/3 = 0x000C codec input control*/

                    /* setup for i2c communication mode */
                    OUTB(devc->osdev, INB (devc->osdev, FUNCTION) | 0x40, FUNCTION);

                    /* setup the GPIO direction control register */
                    OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x018F, GPIO_CONTROL);
                    /* setup GPIO pins mic/output */
                    OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x111, GPIO_DATA);

                    OUTW(devc->osdev, INW(devc->osdev, I2C_CTRL)|0x0100, I2C_CTRL);

                    /* initialize the PCM1796 DAC */
                    i2c_write(devc, XONAR_STX_FRONTDAC, 20, 0x40); /*OS_64*/
                    i2c_write(devc, XONAR_STX_FRONTDAC, 20, 0); /*OS_64*/
                    i2c_write(devc, XONAR_STX_FRONTDAC, 16, mix_scale(75,8));
                    i2c_write(devc, XONAR_STX_FRONTDAC, 17, mix_scale(75,8));
                    i2c_write(devc, XONAR_STX_FRONTDAC, 18, 0x00|0x30|0x80); /*unmute, 24LSB, ATLD */
                    i2c_write(devc, XONAR_STX_FRONTDAC, 19, 0); /*ATS1/FLT_SHARP*/

                    /* initialize the codec 0 */
                    ac97_hwinit(devc);
		    break;

	    case SUBID_XONAR_ST:
	    	    /*GPIO0 = enable output*/
                    /*GPIO1 = frontpanel h/p control*/
                    /*GPIO7 = 0x0080 controls analog out line/HP*/
                    /*GPIO8 = 0x0100 controls mic/line in*/
                    /*GPIO2/3 = 0x000C codec input control*/

                    /* setup for i2c communication mode */
                    OUTB(devc->osdev, INB (devc->osdev, FUNCTION) | 0x40, FUNCTION);

                    /* setup the GPIO direction control register */
                    OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x01FF, GPIO_CONTROL);
                    /* setup GPIO pins mic/output */
                    OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x101, GPIO_DATA);
                    OUTW(devc->osdev, INW(devc->osdev, I2C_CTRL)|0x0100, I2C_CTRL);
                     /* initialize the CS2000 clock chip */
		    cs2000_init (devc, XONAR_ST_CLOCK);

                    /* initialize the PCM1796 DAC */
                    i2c_write(devc, XONAR_ST_FRONTDAC, 20, 0); /*normal*/
                    i2c_write(devc, XONAR_ST_FRONTDAC, 18, 0x00|0x30|0x80); /*unmute, 24LSB, ATLD */
                    i2c_write(devc, XONAR_ST_FRONTDAC, 16, mix_scale(75,8));
                    i2c_write(devc, XONAR_ST_FRONTDAC, 17, mix_scale(75,8));
                    i2c_write(devc, XONAR_ST_FRONTDAC, 19, 0); /*ATS1/FLT_SHARP*/

                    /* initialize the codec 0 */
                    ac97_hwinit(devc);

		    break;

	   case SUBID_XONAR_DS:
			/* GPIO 8 = 1 output enabled 0 mute */
			/* GPIO 7 = 1 lineout enabled 0 mute */
			/* GPIO 6 = 1 mic select 0 line-in select */
			/* GPIO 4 = 1 FP Headphone plugged in */
			/* GPIO 3 = 1 FP Mic plugged in */

                    /* setup for spi communication mode */
                    OUTB(devc->osdev, (INB (devc->osdev, FUNCTION) & ~0x40)|0x32, FUNCTION);
                    /* setup the GPIO direction */
                    OUTW(devc->osdev, INW(devc->osdev, GPIO_CONTROL) | 0x1D0, GPIO_CONTROL);
	            /* setup GPIO Pins */
	            OUTW(devc->osdev, INW(devc->osdev, GPIO_DATA) | 0x1D0, GPIO_DATA);

		    spi_write(devc, XONAR_DS_FRONTDAC, 0x17, 0x1); /* reset */
	 	    spi_write(devc, XONAR_DS_FRONTDAC, 0x7, 0x90); /* dac control */
	 	    spi_write(devc, XONAR_DS_FRONTDAC, 0x8, 0); /* unmute */
	 	    spi_write(devc, XONAR_DS_FRONTDAC, 0xC, 0x22 ); /* powerdown hp */
	 	    spi_write(devc, XONAR_DS_FRONTDAC, 0xD, 0x8); /* powerdown hp */
	 	    spi_write(devc, XONAR_DS_FRONTDAC, 0xA, 0x1); /* LJust/16bit*/
	 	    spi_write(devc, XONAR_DS_FRONTDAC, 0xB, 0x1); /* LJust/16bit*/

	 	    spi_write(devc, XONAR_DS_SURRDAC, 0x1f, 1); /* reset */
	 	    spi_write(devc, XONAR_DS_SURRDAC, 0x3, 0x1|0x20); /* LJust/24bit*/
		   break;

	   default:
		   /* SPI default for anything else, including the */
		   OUTB(devc->osdev, (INB (devc->osdev, FUNCTION) & ~0x40) | 0x80, FUNCTION);
		   break;
  }

  /* check if MPU401 is enabled in MISC register */
  if (INB (devc->osdev, MISC_REG) & 0x40)
    attach_mpu (devc);

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

      switch (i)
	{
	case 0:
	  sprintf (tmp_name, "%s (MultiChannel)", devc->chip_name);
	  caps |= ADEV_DUPLEX;
	  fmt |= AFMT_S32_LE;
	  portc->dac_type = ADEV_MULTICH;
	  switch(devc->model)
  	  {
		case SUBID_XONAR_D1:
		case SUBID_XONAR_DX:
		case SUBID_XONAR_D2:
		case SUBID_XONAR_D2X:
		case SUBID_XONAR_STX:
		case SUBID_XONAR_ST:
			portc->adc_type = ADEV_I2SADC2;
      			break;
		case SUBID_XONAR_DS:
			portc->adc_type = ADEV_I2SADC1;
			break;
		default: 
			portc->adc_type = ADEV_I2SADC1;
      			OUTB (devc->osdev, INB (devc->osdev, REC_ROUTING) | 0x18, REC_ROUTING);
			break;
  	  }

	  portc->min_rate = 32000;
	  portc->max_rate = 192000;
	  portc->min_chan = 2;
	  portc->max_chan = 8;
	  break;

	case 1:
	  sprintf (tmp_name, "%s (Multichannel)", devc->chip_name);
	  caps |= ADEV_DUPLEX | ADEV_SHADOW;
	  fmt |= AFMT_S32_LE;
	  portc->dac_type = ADEV_MULTICH;
          switch(devc->model)
          {
                case SUBID_XONAR_D1:
                case SUBID_XONAR_DX:
                case SUBID_XONAR_D2:
                case SUBID_XONAR_D2X:
                case SUBID_XONAR_STX:
                case SUBID_XONAR_ST:
                        portc->adc_type = ADEV_I2SADC2;
                        break;
                case SUBID_XONAR_DS:
                        portc->adc_type = ADEV_I2SADC1;
                        break;
                default:
                        portc->adc_type = ADEV_I2SADC1;
      			OUTB (devc->osdev, INB (devc->osdev, REC_ROUTING) | 0x18, REC_ROUTING);
                        break;
          }

	  portc->min_rate = 32000;
	  portc->max_rate = 192000;
	  portc->min_chan = 2;
	  portc->max_chan = 8;
	  break;

	case 2:
	  /* if there is no front panel AC97, then skip the device */
	  if (devc->fp_mixer_dev == -1)
	    continue;

	  sprintf (tmp_name, "%s (Front Panel)", devc->chip_name);
	  caps |=
	    ADEV_DUPLEX | ADEV_16BITONLY | ADEV_STEREOONLY | ADEV_SPECIAL;
	  fmt |= AFMT_AC3;
	  portc->dac_type = ADEV_FRONTPANEL;
	  portc->adc_type = ADEV_I2SADC2;
      	  OUTB (devc->osdev, INB (devc->osdev, REC_ROUTING) | 0x18, REC_ROUTING);
	  portc->min_rate = 8000;
	  portc->max_rate = 48000;
	  portc->min_chan = 2;
	  portc->max_chan = 2;
	  break;

	case 3:
	  sprintf (tmp_name, "%s (SPDIF)", devc->chip_name);
	  caps |= ADEV_DUPLEX | ADEV_STEREOONLY | ADEV_SPECIAL;
	  fmt |= AFMT_AC3 | AFMT_S32_LE;
	  portc->dac_type = ADEV_SPDIF;
	  portc->adc_type = ADEV_I2SADC3;
	  portc->min_rate = 32000;
	  portc->max_rate = 192000;
	  portc->min_chan = 2;
	  portc->max_chan = 2;
	  break;
	}

      if ((portc->audiodev =
	   oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, devc->osdev,
				 devc->osdev, tmp_name,
				 &cmi8788_audio_driver,
				 sizeof (audiodrv_t), caps, fmt,
				 devc, -1)) < 0)
	{
	  return 0;
	}
      else
	{
	  if (first_dev == -1)
	    first_dev = portc->audiodev;
	  audio_engines[portc->audiodev]->portc = portc;
	  audio_engines[portc->audiodev]->rate_source = first_dev;
	  if (caps & ADEV_FIXEDRATE)
	    {
	      audio_engines[portc->audiodev]->min_rate = 48000;
	      audio_engines[portc->audiodev]->max_rate = 48000;
	      audio_engines[portc->audiodev]->fixed_rate = 48000;
	    }
	  audio_engines[portc->audiodev]->min_rate = portc->min_rate;
	  audio_engines[portc->audiodev]->max_rate = portc->max_rate;

	  audio_engines[portc->audiodev]->caps |= DSP_CAP_FREERATE;
	  audio_engines[portc->audiodev]->min_channels = portc->min_chan;
	  audio_engines[portc->audiodev]->max_channels = portc->max_chan;
	  portc->open_mode = 0;
	  portc->audio_enabled = 0;
#ifdef CONFIG_OSS_VMIX
	  if (i == 0)
	     vmix_attach_audiodev(devc->osdev, portc->audiodev, -1, 0);
#endif
	}
    }

  /*
   * Setup the default volumes to 90%
   */
  
  devc->mute = 0;

  default_vol = mix_scale(90,8)<<8|mix_scale(90,8);

  devc->playvol[0] =
    cmi8788_mixer_ioctl (devc->cmi_mixer_dev, first_dev,
			 MIXER_WRITE (SOUND_MIXER_PCM), &default_vol);
  devc->playvol[1] =
    cmi8788_mixer_ioctl (devc->cmi_mixer_dev, first_dev,
			 MIXER_WRITE (SOUND_MIXER_REARVOL), &default_vol);
  devc->playvol[2] =
    cmi8788_mixer_ioctl (devc->cmi_mixer_dev, first_dev,
			 MIXER_WRITE (SOUND_MIXER_CENTERVOL), &default_vol);
  devc->playvol[3] =
    cmi8788_mixer_ioctl (devc->cmi_mixer_dev, first_dev,
			 MIXER_WRITE (SOUND_MIXER_SIDEVOL), &default_vol);

  return 1;
}

int
oss_cmi878x_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  unsigned short sub_vendor, sub_id;
  int err;
  cmi8788_devc *devc;

  DDB (cmn_err (CE_CONT, "Entered CMEDIA CMI8788 attach routine\n"));
  pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
  pci_read_config_word (osdev, PCI_DEVICE_ID, &device);
  pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &sub_vendor);
  pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &sub_id);

  if (vendor != CMEDIA_VENDOR_ID || device != CMEDIA_CMI8788)
    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, "CMI8788 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);

  if (device == CMEDIA_CMI8788)
    {
      /* Detect Xonar device */
      if(sub_vendor == ASUS_VENDOR_ID)
      {
        switch(sub_id)
        {
          case SUBID_XONAR_D1:
            devc->chip_name = "Asus Xonar D1 (AV100)";
            break;
          case SUBID_XONAR_DX:
            devc->chip_name = "Asus Xonar DX (AV100)";
            break;
          case SUBID_XONAR_D2:
            devc->chip_name = "Asus Xonar D2 (AV200)";
            break;
          case SUBID_XONAR_D2X:
            devc->chip_name = "Asus Xonar D2X (AV200)";
            break;
          case SUBID_XONAR_STX:
            devc->chip_name = "Asus Xonar Essence STX (AV100)";
            break;
          case SUBID_XONAR_ST:
            devc->chip_name = "Asus Xonar Essence ST (AV100)";
            break;
	  case SUBID_XONAR_DS:
	    devc->chip_name = "Asus Xonar DS (AV66)";
	    break;
          default:
            devc->chip_name = "Asus Xonar (unknown)";
            sub_id = SUBID_GENERIC;
            break;
        }
        devc->model = sub_id;
      }
      else
      {
        /* If not one of the above, regular. */
        devc->model = MDL_CMI8788;
        devc->chip_name = "CMedia CMI8788";
      }
    }
  else
    {
      cmn_err (CE_WARN, "Unknown CMI8788 model\n");
      return 0;
    }

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

  oss_register_device (osdev, devc->chip_name);

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

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

int
oss_cmi878x_detach (oss_device_t * osdev)
{
  cmi8788_devc *devc = (cmi8788_devc *) osdev->devc;

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

  if (devc->mpu_attached)
    unload_cmi8788uart (devc);

  oss_unregister_interrupts (devc->osdev);

  UNMAP_PCI_IOADDR (devc->osdev, 0);
  MUTEX_CLEANUP (devc->mutex);
  MUTEX_CLEANUP (devc->low_mutex);
  MUTEX_CLEANUP (devc->dac_mutex);

  oss_unregister_device (osdev);
  return 1;
}
