/*
 * Purpose: Low level routines for M Audio Revolution 5.1
 */
/*
 * 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_envy24ht_cfg.h"

#include "spdif.h"
#include "envy24ht.h"

static char channel_names[4][10] = {
  "front",
  "c/l",
  "surround",
  "headph"
};

#define BIT(x) (1<<(x))
#define BIT3 BIT(3)

/*----- SPI bus for CODEC communication. */
/* */
#define SPI_CLK 1		/* Clock output to CODEC's, rising edge clocks data. */
#define SPI_DOUT 3		/* Data output to the CODEC. */
#define SPI_CS0n (1<<4)		/* Selects first chip. */
#define SPI_CS1n (1<<5)		/* Selects second chip. */
#define SPI_CC_AK4358 0x02	/* C1:C0 for ak4358. */
#define SPI_CC_AK5365 0x02	/* C1:C0 for ak5365. */
#define WRITEMASK 	0xffff
/*----- Revolution defines. */
/* */
#define REVO51_AK5365 (1)	/* iDevice value for AK5365 A/D. */
#define REVO51_AK4358 (2)	/* iDevice value for AK4358 D/A. */

static unsigned int
GpioReadAll (envy24ht_devc * devc)
{
  return INW (devc->osdev, devc->ccs_base + 0x14);
}

static void
GpioWriteAll (envy24ht_devc * devc, unsigned int data)
{
  OUTW (devc->osdev, 0xffff, devc->ccs_base + 0x18);	/* GPIO direction */
  OUTW (devc->osdev, 0x0000, devc->ccs_base + 0x16);	/* GPIO write mask */

  OUTW (devc->osdev, data, devc->ccs_base + 0x14);
}

static void
GpioWrite (envy24ht_devc * devc, int pos, int bit)
{
  int data = GpioReadAll (devc);

  bit = (bit != 0);

  data &= ~(1 << pos);
  data |= (bit << pos);

  GpioWriteAll (devc, data);
}

void
REVO51_Assert_CS (envy24ht_devc * devc, int iDevice)
/*
*****************************************************************************
* Assert chip select to specified GPIO-connected device.
* iDevice: REVO51_AK5365=ADC, REVO51_AK4358=DAC.
****************************************************************************/
{
  unsigned int dwGPIO;		/* Current GPIO's. */
  dwGPIO = GpioReadAll (devc);	/* Read current GPIO's. */
  dwGPIO |= (SPI_CS0n | SPI_CS1n);	/* Reset CS bits. */
  switch (iDevice)		/* Select CS#. */
    {
    case REVO51_AK4358:
      dwGPIO &= ~SPI_CS0n;
      break;			/* DAC */
    case REVO51_AK5365:
      dwGPIO &= ~SPI_CS1n;
      break;			/* ADC */
    default:
      break;
    }
  GpioWriteAll (devc, dwGPIO);	/* Write hardware. */
}

void
REVO51_DeAssert_CS (envy24ht_devc * devc)
/*
*****************************************************************************
* De-Assert all chip selects.
****************************************************************************/
{
  unsigned int dwGPIO = GpioReadAll (devc);	/* Current GPIO's. */
  dwGPIO |= (SPI_CS0n | SPI_CS1n);	/* Clear CS bits. */
  GpioWriteAll (devc, dwGPIO);	/* Write back to hardware. */
}

/*#define _delay()	oss_udelay(1) */
#define _delay()	{}

void
REVO51_WriteSpiAddr (envy24ht_devc * devc, int iDevice, unsigned char bReg)
/*
*****************************************************************************
* Write the address byte part of the SPI serial stream.
* iDevice: REVO51_AK4358=DAC, REVO51_AK5365=ADC, etc.
****************************************************************************/
{
  unsigned char bHdr;
  unsigned char bNum;
/* Built 8-bit packet header: C1,C0,R/W,A4,A3,A2,A1,A0. */
/* */
  switch (iDevice)
    {
    case REVO51_AK4358:
      bHdr = SPI_CC_AK4358 << 6;
      break;
    case REVO51_AK5365:
      bHdr = SPI_CC_AK5365 << 6;
      break;
    default:
      bHdr = 0;
      break;
    }
  bHdr = bHdr | 0x20 | (bReg & 0x1F);	/* "write" + address. */
/* Write header to SPI. */
/* */
  for (bNum = 0; bNum < 8; bNum++)
    {
      GpioWrite (devc, SPI_CLK, 0);	/* Drop clock low. */
      _delay ();
      GpioWrite (devc, SPI_DOUT, 0x080 & bHdr);	/* Write data bit. */
      _delay ();
      GpioWrite (devc, SPI_CLK, 1);	/* Raise clock. */
      _delay ();
      bHdr <<= 1;		/* Next bit. */
    }
}

void
REVO51_WriteSpiReg (envy24ht_devc * devc, int iDevice, unsigned char bReg,
		    unsigned char bData)
/*
*****************************************************************************
* Writes one register in specified CHIP.
* devc = PCI slot code of specific board.
* iDevice: REVO51_AK4358=DAC, REVO51_AK5365=ADC, etc.
****************************************************************************/
{
  unsigned char bNum;
  GpioWrite (devc, SPI_DOUT, 0);	/* Init SPI signals. */
  GpioWrite (devc, SPI_CLK, 1);	/* */
/* Drop the chip select low. */
/* Wait at least 150 nS. */
/* */
  REVO51_Assert_CS (devc, iDevice);
  _delay ();
/* Write the address byte. */
/* */
  REVO51_WriteSpiAddr (devc, iDevice, bReg);
/* Write the data byte. */
/* */
  for (bNum = 0; bNum < 8; bNum++)
    {
      GpioWrite (devc, SPI_CLK, 0);	/* Drop clock low. */
      _delay ();
      GpioWrite (devc, SPI_DOUT, 0x080 & bData);	/* Write data bit. */
      _delay ();
      GpioWrite (devc, SPI_CLK, 1);	/* Raise clock. */
      _delay ();
      bData <<= 1;		/* Next bit. */
    }
/* De-assert chip selects. */
/* */
  REVO51_DeAssert_CS (devc);
  _delay ();
}


#define GPIO_MUTEn 22		/* Converter mute signal. */
void
REVO51_Mute (envy24ht_devc * devc, int bMute)
/*
*****************************************************************************
* Mutes all outputs if bMute=1.
****************************************************************************/
{
  if (bMute)
    {
/* Soft-mute. Delay currently unspecified, try ½ second. */
      REVO51_WriteSpiReg (devc, REVO51_AK4358, 1, 0x03);
      _delay ();
/* Switch mute transistors on. */
      GpioWrite (devc, GPIO_MUTEn, 0);
    }
  else
    {
/* Switch mute transistors off. Delay currently unspecified, try ½ second. */
      GpioWrite (devc, GPIO_MUTEn, 1);
      _delay ();
/* Release Soft-mute. */
      REVO51_WriteSpiReg (devc, REVO51_AK4358, 1, 0x01);
    }

  devc->mute = bMute;
}


void
REVO51_Set_OutAttn (envy24ht_devc * devc, unsigned char bChan, int iAttn)
/*
*****************************************************************************
* Sets the attenuation on one output channel.
* bChan = Channel number (0..7).
* Channel 0:1 = front, 2:3 = center/sub, 4:5 = rear, 6:7 = headphones.
* Registers are 0x04, 05, 06, 07, 08, 09, 0B, 0C respectively
* iAttn = Number of steps to attenuate CODEC.
* Each step equals .5dB (-127..0)
****************************************************************************/
{
  unsigned char bIndex;
  unsigned char bAttn;
  if (bChan > 7 || iAttn > 0 || iAttn < -127)	/* parameter test */
    {
      cmn_err (CE_CONT, "\ninvalid data! %d=bChan, %d=iAttn", bChan, iAttn);
      return;
    }
  if (bChan < 6)
    bIndex = 0x04 + bChan;	/* for registers 0x04..0x09 */
  else
    bIndex = 0x05 + bChan;	/* for registers 0x0B..0x0C */
  bAttn = (0x80 + (unsigned char) (iAttn + 127));	/* 7F is max volume. */
/* MSB enables attenuation. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, bIndex, bAttn);
}

static void
ADC_Set_Chan (envy24ht_devc * devc, int iChan)
/***************************************************************************
* Makes input selection for ADC.
* 0=mic, 1=line, 2=aux
****************************************************************************/
{
/*Check param */
  if (2 < iChan)
#define GPIO_SDA 6		/* SDA pin */
    cmn_err (CE_CONT, "\nInvalid Input channel parameter");
  else
    REVO51_WriteSpiReg (devc, REVO51_AK5365, 1, (unsigned char) iChan);
}

#define GPIO_SCL 7		/* SCL pin */
static void
start_bit (envy24ht_devc * devc)
/*
*****************************************************************************
* Send I2C Start Bit.
****************************************************************************/
{
  GpioWrite (devc, GPIO_SDA, 1);	/* Make sure DATA high. */
  _delay ();
  GpioWrite (devc, GPIO_SCL, 1);	/* Set clock high. */
  _delay ();
  GpioWrite (devc, GPIO_SDA, 0);	/* Falling edge indicates start bit. */
  _delay ();
  GpioWrite (devc, GPIO_SCL, 0);	/* Start clock train. */
}

static void
byte_out (envy24ht_devc * devc, unsigned char b8)
/*
*****************************************************************************
* Send current 8 input bits in B8. Msb is first written.
****************************************************************************/
{
  int bnum;			/* bit number */
  for (bnum = 7; bnum >= 0; bnum--)
    {
      GpioWrite (devc, GPIO_SDA, b8 & 0x80);	/* Set bit. */
      _delay ();		/* */
      GpioWrite (devc, GPIO_SCL, 1);	/* Set clock high. */
      _delay ();		/* */
      GpioWrite (devc, GPIO_SCL, 0);	/* Return clock low. */
      b8 = b8 << 1;		/* Next position. */
    }
/* No ACK, but we need to clock a false "9th" bit. */
  _delay ();			/* */
  GpioWrite (devc, GPIO_SCL, 1);	/* Set clock high. */
  _delay ();			/* */
  GpioWrite (devc, GPIO_SCL, 0);	/* Return clock low. */
}

static void
stop_bit (envy24ht_devc * devc)
/*
*****************************************************************************
* Send I2C Stop Bit.
****************************************************************************/
{
  GpioWrite (devc, GPIO_SDA, 0);	/* Make sure data is low. */
  _delay ();
  GpioWrite (devc, GPIO_SCL, 1);	/* Set clock high. */
  _delay ();
  GpioWrite (devc, GPIO_SDA, 1);	/* Rising edge indicates stop bit. */
}
static void
PT2258S_Write_Data (envy24ht_devc * devc, int cnt, unsigned char *aData)
/*
************************************************************************ *****
* Write an array of bytes (aData) to address VADDR in PT2258S.
* Assumes I2C Bus is inactive, i.e., SCL=1,SDA=1 and both are outputs.
* This routine waits for the byte to complete writing.
************************************************************************ ****/
{
  int i;
  start_bit (devc);		/* Send start bit. */
  byte_out (devc, (unsigned char) 0x80);	/* Set address to write data to. */
  for (i = 0; i < cnt; i++)
    byte_out (devc, aData[i]);	/* Write data to address. */
  stop_bit (devc);		/* Stop bit ends operation. */
}
static void
PT2258S_Write_Byte (envy24ht_devc * devc, unsigned char vdata)
/*
*****************************************************************************
* Write a byte VDATA to address VADDR in PT2258S.
* Assumes I2C Bus is inactive, i.e., SCL=1,SDA=1 and both are outputs.
* This routine waits for the byte to complete writing.
****************************************************************************/
{
  start_bit (devc);		/* Send start bit. */
  byte_out (devc, (unsigned char) 0x80);	/* Set address to write data to. */
  byte_out (devc, vdata);	/* Write data to address. */
  stop_bit (devc);		/* Stop bit ends operation. */
}

static void
PT2258S_Set_Attn (envy24ht_devc * devc, unsigned char bChan, int iAttn)
/*
************************************************************************ *****
* Set the attenuation of a specific channel.
* bChan = 0..5.
* iAttn = 0..-79.
************************************************************************ ****/
{
  static unsigned char bXlat[] = { 0x90, 0x50, 0x10, 0x30, 0x70, 0xB0 };
  unsigned char aAttn[2];
  aAttn[1] = (-iAttn) % 10;	/* 1's digit */
  aAttn[0] = (-iAttn) / 10;	/* 10's digit */
/* Check parameters. */
  if ((bChan > 5) || (iAttn < (-79) || iAttn > 0))
    {
      cmn_err (CE_CONT, "\nPT2258S_Set_Attn() parameter out of range!");
      return;
    }
/* Always set 10's digit, then 1's. */
  aAttn[0] += bXlat[bChan] - 0x10;
  aAttn[1] += bXlat[bChan];
  PT2258S_Write_Data (devc, 2, aAttn);
}
static void
PT2258S_Mute (envy24ht_devc * devc, int bMute)
/*
*****************************************************************************
* Mute all 6 outputs of the monitoring volume control.
* Unmuting returns volume to previous levels.
****************************************************************************/
{
  if (bMute)
    PT2258S_Write_Byte (devc, (unsigned char) 0xF9);
  else
    PT2258S_Write_Byte (devc, (unsigned char) 0xF8);
}


static void
REVO51_Set_48K_Mode (envy24ht_devc * devc)
/*
*****************************************************************************
* Sets Chip and Envy24 for 8kHz-48kHz sample rates.
****************************************************************************/
{
/* ICE MCLK = 256x. */
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 2) & ~BIT3,
	devc->mt_base + 2);
/* DFS=normal, RESET. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x4E);
/* DFS=normal, NORMAL OPERATION. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x4F);
}

static void
REVO51_Set_96K_Mode (envy24ht_devc * devc)
/*
*****************************************************************************
* Sets CODEC and Envy24 for 60kHz-96kHz sample rates.
****************************************************************************/
{
/* ICE MCLK = 256x. */
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 2) & ~BIT3,
	devc->mt_base + 2);
/* DFS=double-speed, RESET. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x5E);
/* DFS=double-speed, NORMAL OPERATION. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x5F);
}

static void
REVO51_Set_192K_Mode (envy24ht_devc * devc)
/*
*****************************************************************************
* Sets CODEC and Envy24 for 120kHz-192kHz sample rate.
****************************************************************************/
{
/* ICE MCLK = 128x. */
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 2) | BIT3,
	devc->mt_base + 2);
  _delay ();
/*----- SET THE D/A. */
/* DFS=quad-speed, RESET. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x6E);
  _delay ();
/* DFS=quad-speed, NORMAL OPERATION. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x6F);
/*------ POWER DOWN THE A/D -- doesn't support 192K. */
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 0, 0x00);
}

static int
set_dac (envy24ht_devc * devc, int reg, int level)
{
  if (level < 0)
    level = 0;
  if (level > 0x7f)
    level = 0x7f;

  REVO51_WriteSpiReg (devc, REVO51_AK4358, reg, level | 0x80);

  return level;
}

static void
AK4358_Init (envy24ht_devc * devc)
{
/*===== AK4358 D/A initialization. Leave soft-muted. */
/* */
/* Power down, reset, normal mode. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x00);
/* Power up, reset, normal mode */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x4E);
/* Reset timing, Mode 3(I2S), disable auto clock detect, sharp roll off. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 0, 0x06);
/* Soft mute, reset timing. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 1, 0x02);
/* De-emphasis off. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 3, 0x01);
/* Max volume on all 8 channels. */
  set_dac (devc, 0x04, 0x7f);
  set_dac (devc, 0x05, 0x7f);
  set_dac (devc, 0x06, 0x7f);
  set_dac (devc, 0x07, 0x7f);
  set_dac (devc, 0x08, 0x7f);
  set_dac (devc, 0x09, 0x7f);
  set_dac (devc, 0x0b, 0x7f);
  set_dac (devc, 0x0c, 0x7f);

/* Datt mode 0, DZF non-invert, DCLK polarity 0, PCM mode, DCKS 512fs, TDM normal. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 0xA, 0x00);
/* DZF control disabled. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 0xD, 0x00);
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 0xE, 0x00);
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 0xF, 0x00);
/* Power up, normal operation. */
  REVO51_WriteSpiReg (devc, REVO51_AK4358, 2, 0x4F);
}

static void
PT2258_init (envy24ht_devc * devc)
{
/*===== PT2258 initialization. */
/* */
/* Initializes and clears register . */
  PT2258S_Write_Byte (devc, (unsigned char) 0xC0);
/*Example of 2-line command controlling all channels. */
/*This sets all channels to max volume. */
/*PT2258S_Write_Byte(devc, (unsigned char)0xE0); */
/*PT2258S_Write_Byte(devc, (unsigned char)0xD0); */
/* set volumes */
  PT2258S_Set_Attn (devc, 0, 0);
  PT2258S_Set_Attn (devc, 1, 0);
  PT2258S_Set_Attn (devc, 2, 0);
  PT2258S_Set_Attn (devc, 3, 0);
  PT2258S_Set_Attn (devc, 4, 0);
  PT2258S_Set_Attn (devc, 5, 0);
/*mute, true or false */
  PT2258S_Mute (devc, 0);
}

static void
REVO51_set_recsrc (envy24ht_devc * devc, int src)
{
  devc->recsrc = src;

  REVO51_WriteSpiReg (devc, REVO51_AK5365, 1, src);
}

static void
AK5365_Init (envy24ht_devc * devc)
{
/*===== AK5365 2-ch A/D initialization. Leave soft-muted. */
/* */
/* Power down. */
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 0, 0x00);
/* Set input to (LINE IN). */
  REVO51_set_recsrc (devc, 0);
/* Clock freq 256fs, I2S mode, mute off. */
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 2, 0x08);
/* ALC settings (ALC is disabled, so rewrite default values). */
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 3, 0x2B);
/* Neutral analog and digital gain. */
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 4, 0x7F);
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 5, 0x7F);
/* ALC settings (ALC is disabled, so rewrite default values). */
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 6, 0x28);
/* ALC settings (ALC is disabled, so rewrite default values). */
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 7, 0x89);
/* Power up. */
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 0, 0x01);
}

static void
revo51_set_rate (envy24ht_devc * devc)
{
  int tmp;

  tmp = INB (devc->osdev, devc->mt_base + 0x02);
  if (devc->speed <= 48000)
    {
      REVO51_Set_48K_Mode (devc);
      OUTB (devc->osdev, tmp & ~BIT (3), devc->mt_base + 0x02);
      return;
    }

  if (devc->speed <= 96000)
    {
      REVO51_Set_96K_Mode (devc);

      return;
    }

  REVO51_Set_192K_Mode (devc);
  OUTB (devc->osdev, tmp | BIT (3), devc->mt_base + 0x02);
}

static int
revo51_audio_ioctl (envy24ht_devc * devc, envy24ht_portc * portc, unsigned int cmd,
		    ioctl_arg arg)
{
  int value;

  switch (cmd)
    {
    case SNDCTL_DSP_GET_RECSRC_NAMES:
      return oss_encode_enum ((oss_mixer_enuminfo *) arg, "mic line aux", 0);
      break;

    case SNDCTL_DSP_GET_RECSRC:
      return *arg = devc->recsrc;
      break;

    case SNDCTL_DSP_SET_RECSRC:
      value = *arg;
      if (value < 0 || value > 2)
	return OSS_EINVAL;
      REVO51_set_recsrc (devc, value);
      return *arg = devc->recsrc;
      break;

    case SNDCTL_DSP_GET_PLAYTGT:
    case SNDCTL_DSP_SET_PLAYTGT:
      return *arg = 0;
      break;

    case SNDCTL_DSP_GET_PLAYTGT_NAMES:
      return oss_encode_enum ((oss_mixer_enuminfo *) arg, portc->name, 0);
      break;


    default:
      return OSS_EINVAL;
    }
}

static int
set_reclevel (envy24ht_devc * devc, int value)
{
  int left, right;

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

  if (left > 0x98)
    left = 0x98;
  if (right > 0x98)
    right = 0x98;

  value = left | (right << 8);

  devc->reclevel = value;

  REVO51_WriteSpiReg (devc, REVO51_AK5365, 4, left);
  REVO51_WriteSpiReg (devc, REVO51_AK5365, 5, right);

  return value;
}

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

  if (cmd == SNDCTL_MIX_READ)
    switch (ctrl)
      {
      case 0:
	return devc->mute;

      case 1:
	return devc->recsrc;

      case 2:
	return devc->reclevel;

      default:
	return OSS_EINVAL;
      }

  if (cmd == SNDCTL_MIX_WRITE)
    switch (ctrl)
      {
      case 0:
	value = !!value;
	REVO51_Mute (devc, value);
	return devc->mute;

      case 1:
	if (value < 0 || value > 2)
	  return devc->recsrc;
	REVO51_set_recsrc (devc, value);
	return devc->recsrc;

      case 2:
	return set_reclevel (devc, value);

      default:
	return OSS_EINVAL;
      }

  return OSS_EINVAL;
}

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

  if (cmd == SNDCTL_MIX_READ)
    {
      if (ctrl < 0 || ctrl > 4)
	return OSS_EIO;

      return devc->gains[ctrl];
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
      int left, right;

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

      switch (ctrl)
	{
	case 0:		/* Front */
	  left = set_dac (devc, 0x04, left);
	  right = set_dac (devc, 0x05, right);
	  break;

	case 1:		/* Center */
	  left = set_dac (devc, 0x06, left);
	  right = left;
	  break;

	case 2:		/* LFE */
	  left = set_dac (devc, 0x07, left);
	  right = left;
	  break;

	case 3:		/* Surround */
	  left = set_dac (devc, 0x08, left);
	  left = set_dac (devc, 0x09, right);
	  break;

	case 4:		/* Headphones */
	  left = set_dac (devc, 0x0b, left);
	  left = set_dac (devc, 0x0c, right);
	  break;

	default:
	  return OSS_EINVAL;
	}

      value = left | (right << 8);
      return devc->gains[ctrl] = value;
    }

  return OSS_EINVAL;
}

static int
set_mongain (envy24ht_devc * devc, int reg, int value)
{
  if (value < 0)
    value = 0;
  if (value > 79)
    value = 79;

  PT2258S_Set_Attn (devc, reg, value - 79);
  return value;
}

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

  if (cmd == SNDCTL_MIX_READ)
    {
      if (ctrl < 0 || ctrl > 3)
	return OSS_EIO;

      return devc->monitor[ctrl];
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
      int left, right;

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

      switch (ctrl)
	{
	case 0:		/* Mic */
	  left = set_mongain (devc, 0, left);
	  right = set_mongain (devc, 1, right);
	  break;

	case 1:		/* Line */
	  left = set_mongain (devc, 2, left);
	  right = set_mongain (devc, 3, right);
	  break;

	case 2:		/* Aux */
	  left = set_mongain (devc, 4, left);
	  right = set_mongain (devc, 5, right);
	  break;

	case 3:		/* Mute */
	  value = !!value;
	  PT2258S_Mute (devc, value);
	  return devc->monitor[3] = value;
	  break;

	default:
	  return OSS_EINVAL;
	}

      value = left | (right << 8);
      return devc->monitor[ctrl] = value;
    }

  return OSS_EINVAL;
}

 /*ARGSUSED*/ static int
revo51_mixer_init (envy24ht_devc * devc, int dev, int g)
{
  int group = 0;
  int err;

  if ((err = mixer_ext_create_control (dev, group,
				       0, revo51_set_control,
				       MIXT_ONOFF,
				       "ENVY24_MUTE", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       0, revo51_set_ak4358,
				       MIXT_STEREOSLIDER,
				       "ENVY24_FRONT", 0x7f,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       1, revo51_set_ak4358,
				       MIXT_SLIDER,
				       "ENVY24_CENTER", 0x7f,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       2, revo51_set_ak4358,
				       MIXT_SLIDER,
				       "ENVY24_LFE", 0x7f,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       3, revo51_set_ak4358,
				       MIXT_STEREOSLIDER,
				       "ENVY24_SURROUND", 0x7f,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       4, revo51_set_ak4358,
				       MIXT_STEREOSLIDER,
				       "ENVY24_HEADPH", 0x7f,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       2, revo51_set_control,
				       MIXT_STEREOSLIDER,
				       "ENVY24_REC", 0x98,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       1, revo51_set_control,
				       MIXT_ENUM,
				       "ENVY24_RECSRC", 3,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  mixer_ext_set_strings (dev, err, "mic line aux", 0);

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

  if ((err = mixer_ext_create_control (dev, group,
				       3, revo51_set_monitor,
				       MIXT_ONOFF,
				       "MON_MUTE", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       0, revo51_set_monitor,
				       MIXT_STEREOSLIDER,
				       "ENVY24_MIC", 79,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       1, revo51_set_monitor,
				       MIXT_STEREOSLIDER,
				       "ENVY24_LINE", 79,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       2, revo51_set_monitor,
				       MIXT_STEREOSLIDER,
				       "ENVY24_AUX", 79,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  return 0;
}

static void
revo51_card_init (envy24ht_devc * devc)
{

  int i;

#if 1
  OUTW (devc->osdev, 0xffff, devc->ccs_base + 0x18);	/* GPIO direction */
  OUTW (devc->osdev, 0x0000, devc->ccs_base + 0x16);	/* GPIO write mask */
  OUTW (devc->osdev, 0xffff, devc->ccs_base + 0x14);	/* Initial bit state */

  OUTB (devc->osdev, 0xff, devc->ccs_base + 0x1a);	/* GPIO direction for bits 16:22 */
  OUTB (devc->osdev, 0x00, devc->ccs_base + 0x1f);	/* GPIO mask for bits 16:22 */
  OUTB (devc->osdev, 0xff, devc->ccs_base + 0x1e);	/* GPIO data for bits 16:22 */
#endif

  memcpy (devc->channel_names, channel_names, sizeof (channel_names));
  AK4358_Init (devc);
  AK5365_Init (devc);
  PT2258_init (devc);
  ADC_Set_Chan (devc, 0);	/* TODO */
  REVO51_Set_48K_Mode (devc);

  for (i = 0; i < 5; i++)
    devc->gains[i] = 0x7f7f;
  for (i = 0; i < 3; i++)
    devc->monitor[i] = 79 | (79 << 8);
  devc->monitor[3] = 0;		/* Unmuted */

  set_reclevel (devc, 0x7f7f);	/* +0 dB */

  REVO51_Mute (devc, 0);
}

envy24ht_auxdrv_t envy24ht_revo51_auxdrv = {
  revo51_card_init,
  revo51_mixer_init,
  revo51_set_rate,
  NULL,
  NULL,
  NULL,				/* revo51_private1 */
  revo51_audio_ioctl
};
