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

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

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

#define CCLK	(1<<1)
#define CDIN	(1<<2)		/* Currently not connected */
#define CDTI	(1<<3)
#define CSDIG	(1<<4)
#define CSN1	(1<<5)
#define CSN2	(1<<6)

#define WRITEMASK 	(CSN1|CSN2|CDTI|CCLK)

#define DAC1 0x01		/* 2ch Front DAC (AK4381) */
#define DAC2 0x03		/* 6ch Surround DAC (AK4355) */


static void
writereg (envy24ht_devc * devc, int csn, int chip, unsigned char reg,
	  unsigned char val)
{
  int i;
  unsigned short v, tmp;

  tmp = INW (devc->osdev, devc->ccs_base + 0x14) & ~(csn | CCLK | CDTI);
  OUTW (devc->osdev, tmp, devc->ccs_base + 0x14);

  reg = (reg & 0x1f) | 0x20 | (chip << 6);	/* Chip address (variable), write */
  /* Address bits */

  for (i = 7; i >= 0; i--)
    {
      v = (reg & (1 << i)) ? CDTI : 0;
      OUTW (devc->osdev, v | tmp, devc->ccs_base + 0x14);	/* Tack */
      OUTW (devc->osdev, v | CCLK | tmp, devc->ccs_base + 0x14);	/* Tick */
    }

  /* Data bits */

  for (i = 7; i >= 0; i--)
    {
      v = (val & (1 << i)) ? CDTI : 0;
      OUTW (devc->osdev, v | tmp, devc->ccs_base + 0x14);	/* Tack */
      OUTW (devc->osdev, v | CCLK | tmp, devc->ccs_base + 0x14);	/* Tick */
    }

  OUTW (devc->osdev, v | tmp, devc->ccs_base + 0x14);	/* Tack */
  OUTW (devc->osdev, tmp | csn | CDTI | CCLK, devc->ccs_base + 0x14);	/* Release */

}

static void
writedac1 (envy24ht_devc * devc, unsigned char reg, unsigned char data)
{
  writereg (devc, CSN1, DAC1, reg, data);
  devc->dac1val[reg] = data;
}

static void
writedac2 (envy24ht_devc * devc, unsigned char reg, unsigned char data)
{
  writereg (devc, CSN2, DAC2, reg, data);
  devc->dac2val[reg] = data;
}

static void
revo71_mute (envy24ht_devc * devc, int mute)
{
  if (mute)
    {
      writedac1 (devc, 1, devc->dac1val[1] | BIT (0));
      writedac2 (devc, 1, devc->dac2val[1] | BIT (1));
      oss_udelay (1000);
/*      OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x1e) & ~BIT(22-16), */
/*          devc->ccs_base + 0x1e); */
    }
  else
    {
/*      OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x1e) | ~BIT(22-16), */
/*          devc->ccs_base + 0x1e); */
      oss_udelay (1000);
      writedac1 (devc, 1, devc->dac1val[1] & ~BIT (0));
      writedac2 (devc, 1, devc->dac2val[1] & ~BIT (1));
    }
}

static void
revo71_card_init (envy24ht_devc * devc)
{
  int i;

  OUTL (devc->osdev, WRITEMASK | 0x400000, devc->ccs_base + 0x18);	/* GPIO direction */
  OUTW (devc->osdev, ~WRITEMASK, devc->ccs_base + 0x16);	/* GPIO write mask */
  OUTB (devc->osdev, 0x40, devc->ccs_base + 0x1f);
  OUTW (devc->osdev, WRITEMASK, 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 */

#if 0
  for (i = 0; i < 7; i++)
    {
      OUTW (devc->osdev, 1 << i, devc->ccs_base + 0x14);	/* Test bit */
      OUTW (devc->osdev, 0, devc->ccs_base + 0x14);	/* Test bit */
    }
#endif

  OUTW (devc->osdev, WRITEMASK, devc->ccs_base + 0x14);	/* Initial bit state */
  oss_udelay (10);

  revo71_mute (devc, 1);
/*
 * Init front DAC (AK4381)
 */
  writedac1 (devc, 0x00, 0x0c);
  writedac1 (devc, 0x01, 0x03);
  writedac1 (devc, 0x02, 0x00);

  writedac1 (devc, 0x03, 0xff);	/* Initial volume */
  writedac1 (devc, 0x04, 0xff);	/* Initial volume */

  writedac1 (devc, 0x00, 0x0f);
/*
 * Init surround DAC (AK4355)
 */
  writedac2 (devc, 0x01, 0x02);
  writedac2 (devc, 0x00, 0x06);
  oss_udelay (10);
  writedac2 (devc, 0x02, 0x0e);
  writedac2 (devc, 0x03, 0x01);

  for (i = 4; i < 10; i++)
    writedac2 (devc, i, 0xff);	/* Initial volumes */
  writedac2 (devc, 0x0a, 0x00);

  writedac2 (devc, 0x01, 0x01);
  revo71_mute (devc, 0);
}

static void
revo71_set_rate (envy24ht_devc * devc)
{
  int rate = devc->speed, i;
  unsigned char tmp;

  if (devc->speed == devc->prev_speed)
    return;
  devc->prev_speed = devc->speed;

  revo71_mute (devc, 1);

  /* Pulse the PRST# signal to reset converters */
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x05) | 0x80,
	devc->mt_base + 0x05);
  oss_udelay (5000);
  OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x05) & ~0x80,
	devc->mt_base + 0x05);
  oss_udelay (5000);

  tmp = 0x03;
  if (rate <= 48000)
    tmp |= 0x00;
  else
    {
      if (rate <= 96000)
	tmp |= 0x04;
      else
	tmp |= 0x08;
    }

  /* Front DAC */

  writedac1 (devc, 0, 0x0c);
  writedac1 (devc, 1, tmp);
  writedac1 (devc, 2, 0x00);
  writedac1 (devc, 3, devc->dac1val[3]);
  writedac1 (devc, 4, devc->dac1val[4]);
  writedac1 (devc, 0, 0x0f);

  /* Surround DAC */
  writedac2 (devc, 1, 0x02);
  writedac2 (devc, 0, 0x06);

  tmp = 0x0e;

  if (devc->speed > 60000)
    {
      if (devc->speed > 120000)
	tmp |= 0x20;
      else
	tmp |= 0x10;
    }
  writedac2 (devc, 2, tmp);
  writedac2 (devc, 3, 0x01);

  for (i = 4; i < 10; i++)
    writedac2 (devc, i, devc->dac2val[i]);

  writedac2 (devc, 0xa, 0x00);
  writedac2 (devc, 1, 0x03);

  revo71_mute (devc, 0);
}

static int
revo71_set_akm (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 -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 */
	  writedac1 (devc, 0x03, left);
	  writedac1 (devc, 0x04, right);
	  break;

	case 1:		/* Rear */
	  writedac2 (devc, 0x06, left);
	  writedac2 (devc, 0x07, right);
	  break;

	case 2:		/* Center */
	  writedac2 (devc, 0x04, left);
	  right = left;
	  break;

	case 3:		/* LFE */
	  writedac2 (devc, 0x05, left);
	  right = left;
	  break;

	case 4:		/* Surround */
	  writedac2 (devc, 0x08, left);
	  writedac2 (devc, 0x09, right);
	  break;

	default:
	  return -EINVAL;
	}

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

  return -EINVAL;
}

static int
revo71_mixer_init (envy24ht_devc * devc, int dev, int group)
{
  int i, err;

  for (i = 0; i < 6; i++)
    devc->gains[i] = 0xffff;

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

  if ((err = mixer_ext_create_control (dev, group,
				       0, revo71_set_akm,
				       MIXT_STEREOSLIDER,
				       "GAIN_FRONT", 255,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       1, revo71_set_akm,
				       MIXT_STEREOSLIDER,
				       "GAIN_REAR", 255,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       2, revo71_set_akm,
				       MIXT_MONOSLIDER,
				       "GAIN_CENTER", 255,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       3, revo71_set_akm,
				       MIXT_MONOSLIDER,
				       "GAIN_LFE", 255,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       4, revo71_set_akm,
				       MIXT_STEREOSLIDER,
				       "GAIN_surround", 255,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  return 0;
}

envy24ht_auxdrv_t envy24ht_revo71_auxdrv = {
  revo71_card_init,
  revo71_mixer_init,
  revo71_set_rate,
  NULL,
  NULL,
  NULL,				/* revo71_private1 */
};
