/*
 * Purpose: Card specific routines for M Audio Delta TDIF
 */
/*
 * 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_config.h"
#include "ac97.h"
#include "envy24.h"
#include "envy24_tdif.h"

#define CDC_CLK 1		/* Clock input to the CODEC's, rising edge clocks data. */
#define CDC_DIN 2		/* Data input to Envy from the CODEC. */
#define CDC_DOUT 3		/* Data output from Envy to the CODEC. */
#define CS8_CS 2		/* Chip select (0=select) for the SPDIF tx/rx. */
#define CDC_CS 3		/* Chip select (0=select) for the CODEC. */
#define CS_ASSERT 0		/* Asserted chip select (selects are inverted). */
#define CS_RELEASE 1		/* Idle chip select (selects are inverted). */


#define BIT0		0x01
#define BIT1		0x02
#define BIT2		0x04
#define BIT3		0x08
#define BIT4		0x10
#define BIT5		0x20
#define BIT6		0x40
#define BIT7		0x80

#define CS8_CLK CDC_CLK
#define CS8_DIN CDC_DIN
#define CS8_DOUT CDC_DOUT
#define CS_TRUE CS_ASSERT
#define CS_FALSE CS_RELEASE
#define CS8_ADDR 0x20		/* Chip SPI/I2C address */
#define CS8_RD 0x01
#define CS8_WR 0x00

#if 0
# define PRT_STATUS(v) outb(v&0xff, 0x378)
#else
# define PRT_STATUS(v)
#endif

/* -----	DEFINITION OF GPIOs     ----- */

#define	FPGA_PROGRAM_L		7	/* FPGA program control select line (active low) */
#define FPGA_CLK		1	/* FPGA program clock */
#define EXT_REG_CLK		0	/* Was GPIO Fast mode (debug) */
#define FPGA_D0			3	/* FPGA program data */

#define LATCH_EN_L		4	/* Strobe for external latch (active low) */
#define FPGA_MUTE		5

#define FPGA_INIT_STATE	0xFC	/* Init state for extern register to allow */
				/* FPGA initialization. */

#define FPGA_MASTER		5

extern int envy24_gain_sliders;
extern int envy24_virtualout;
extern int envy24_zerolatency;	/* Testing in progress */

static void
WriteExternReg (envy24_devc * devc, unsigned char bData)
/* Initialize external register by serially shifting the data out */
/* and then strobe the external latch. */
{
  int i;

  devc->gpio_tmp = bData;

  WriteGPIObit (devc, LATCH_EN_L, 0);	/* DISABLE External Latch */

  /* FPGA clock to low */
  WriteGPIObit (devc, EXT_REG_CLK, 0);

  for (i = 0; i < 8; i++)
    {
      /* Present data */
      WriteGPIObit (devc, FPGA_D0, !!(bData & 0x80));
      /* clock data in */
      WriteGPIObit (devc, EXT_REG_CLK, 1);
      WriteGPIObit (devc, EXT_REG_CLK, 0);
      /* next bit */
      bData = bData << 1;
    }

  WriteGPIObit (devc, LATCH_EN_L, 1);	/* STROBE External Latch */
  WriteGPIObit (devc, LATCH_EN_L, 0);	/* DISABLE External Latch */
}

static void
WriteExternRegBit (envy24_devc * devc, int bit, int value)
{
  devc->gpio_tmp &= ~(1 << bit);
  if (value)
    devc->gpio_tmp |= (1 << bit);
  WriteExternReg (devc, devc->gpio_tmp);
}

static void
tdif_pause (int n)
/*
*****************************************************************************
*  Programmable pause.
****************************************************************************/
{
  static int d;
  int i;

  for (i = 0; i < n; i++)
    for (d = 1000L; d--;);
}

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


static void
TDIF_InitFPGA (envy24_devc * devc)
/*
*****************************************************************************
*  Initialize FPGA
*  wInst = PCI slot code of specific board.
*  Hardware = Spartan II FPGA (XC2S50-5TQ144)
****************************************************************************/
{
  int bReadByte;
  int i, j;

#if 0
  if (envy24_read_cci (devc, 0x20) & 0x40)
    {
      DDB (cmn_err (CE_CONT, "envy24: FPGA is already up and running\n"));
      return;
    }
#endif

  cmn_err (CE_CONT, "Envy24: Loading FPGA - please wait\n");

  /*Init external register */
  WriteGPIObit (devc, FPGA_PROGRAM_L, 1);	/*DISABLE FPGA Programming */
  WriteGPIObit (devc, LATCH_EN_L, 0);	/*DISABLE External Latch */

  bReadByte = FPGA_INIT_STATE;

  WriteExternReg (devc, bReadByte);

  /*Pull PROGRAM# pin LOW to start loading configuration data */
  WriteGPIObit (devc, FPGA_PROGRAM_L, 0);
  tdif_pause (10);
  WriteGPIObit (devc, FPGA_PROGRAM_L, 1);

  for (j = 0; j < sizeof (fpga_code); j++)
    {
      bReadByte = fpga_code[j];
      for (i = 0; i < 8; i++)
	{
	  /*FPGA clock to low */
	  WriteGPIObit (devc, FPGA_CLK, 0);
	  /*Present data */
	  WriteGPIObit (devc, FPGA_D0, !!(bReadByte & 0x80));
	  /*clock data in */
	  WriteGPIObit (devc, FPGA_CLK, 1);

	  /*next bit */
	  bReadByte = bReadByte << 1;
	}

    }

  /*Some clocks to start FPGA */
  for (i = 0; i < 64; i++)
    {
      /*FPGA clock to low */
      WriteGPIObit (devc, FPGA_CLK, 0);
      /*clock data in */
      WriteGPIObit (devc, FPGA_CLK, 1);
    }

  if (!(envy24_read_cci (devc, 0x20) & 0x40))
    cmn_err (CE_CONT, "Envy24: FPGA failed to initialize\n");
  else
    DDB (cmn_err (CE_CONT, "FPGA is up and running\n"));

  oss_udelay (10);
  WriteExternRegBit (devc, FPGA_MUTE, 1);	/* Keep it still muted */

}

static void
write_tdif_codec (envy24_devc * devc, int bRegister, unsigned char bData)
/*

*****************************************************************************
*  Writes a byte to a specific register of the Delta-AP CODEC.
*  Register must be (0..15).
****************************************************************************/
{
  unsigned char bMask;

  bRegister = (bRegister & 0x0F) | 0xA0;	/* Add I2C address field. */

  /* Assert the CODEC chip select and wait at least 150 nS. */
  /* */
  WriteExternRegBit (devc, CDC_CS, CS_TRUE);

  /* Write the register address byte. */
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop SPI clock low. */
      WriteGPIObit (devc, CDC_CLK, 0);

      /* Write current data bit. */
      if (bMask & bRegister)
	WriteGPIObit (devc, CDC_DOUT, 1);
      else
	WriteGPIObit (devc, CDC_DOUT, 0);

      /* Raise SPI clock to "clock data in". */
      WriteGPIObit (devc, CDC_CLK, 1);
    }


  /* Write the data byte. */
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop SPI clock low. */
      WriteGPIObit (devc, CDC_CLK, 0);

      /* Write current data bit. */
      if (bMask & bData)
	WriteGPIObit (devc, CDC_DOUT, 1);
      else
	WriteGPIObit (devc, CDC_DOUT, 0);

      /* Raise SPI clock to "clock data in". */
      WriteGPIObit (devc, CDC_CLK, 1);
    }

  /* De-assert chip select. */
  /* */
  WriteExternRegBit (devc, CDC_CS, CS_FALSE);
}

static void write_tdif_spdif_reg (envy24_devc * devc, int bRegister,
				  int bData);
static int read_tdif_spdif_reg (envy24_devc * devc, int bRegister);

static void
tdif_card_init (envy24_devc * devc)
{
  WriteExternReg (devc, 0xfc);
  TDIF_InitFPGA (devc);
  write_tdif_spdif_reg (devc, 4, read_tdif_spdif_reg (devc, 4) & (~BIT0));
  WriteGPIObit (devc, FPGA_MASTER, 1);
  WriteExternRegBit (devc, 4, 1);	/* Disable TDIF master PLL */
  WriteExternRegBit (devc, 6, 0);	/* Don't route PLL master clock to Envy24. */

  write_tdif_codec (devc, 0, 0x07);
  write_tdif_codec (devc, 1, 0x00);
  write_tdif_codec (devc, 2, 0x60);
  write_tdif_codec (devc, 3, 0x0d);
  write_tdif_codec (devc, 4, 0x7f);
  write_tdif_codec (devc, 1, 0x03);
  write_tdif_codec (devc, 5, 0x7f);
}

static void
tdif_card_uninit (envy24_devc * devc)
{
  WriteGPIObit (devc, FPGA_PROGRAM_L, 1);	/*DISABLE FPGA Programming */
  WriteGPIObit (devc, LATCH_EN_L, 0);	/*DISABLE External Latch */
  WriteGPIObit (devc, FPGA_PROGRAM_L, 0);
}

static int
envy24_set_tdif (int dev, int ctrl, unsigned int cmd, int value)
{
  envy24_devc *devc = mixer_devs[dev]->devc;
  int level;
  static unsigned char levels[] = { 0x60, 0x6f, 0x7f };

  if (ctrl >= 0xff)
    return -EINVAL;

  if (cmd == SNDCTL_MIX_READ)
    {
      if (envy24_gain_sliders)
	return devc->akm_gains[ctrl];
      return devc->akm_gains[ctrl];
    }
  else if (cmd == SNDCTL_MIX_WRITE)
    {

      if (envy24_gain_sliders)
	level = value & 0xff;
      else
	{
	  if (value > 2)
	    return -EINVAL;
	  level = levels[value];
	}

      write_tdif_codec (devc, 4, level);
      write_tdif_codec (devc, 5, level);
      return devc->akm_gains[ctrl] = value;
    }
  return -EINVAL;
}


static int
tdif_mix_init (envy24_devc * devc, int dev, int group)
{
  int i, mask = devc->outportmask, err, skip, codec, ports;
  int typ = MIXT_ENUM, range = 3;
  char tmp[64];

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

  skip = devc->skipdevs;
  if (skip != 2)
    skip = 1;

  if (envy24_gain_sliders)
    {
      typ = MIXT_MONOSLIDER;
      range = 164;

      for (i = 0; i < 0xff; i++)
	devc->akm_gains[i] = 0x7f;
    }
  else
    for (i = 0; i < 0xff; i++)
      devc->akm_gains[i] = 0;

  for (i = 0; i < 8; i += skip)
    {

      if (!(mask & (1 << i)))
	continue;		/* Not present */

      if (devc->skipdevs == 2)
	{
	  if (i == 8)
	    strcpy (tmp, "ENVY24_SPDOUT");
	  else
	    sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2);

	  codec = (i > 1) ? AKM_B : AKM_A;
	  ports = 0x0c;		/* Both output ports */
	  if ((err = mixer_ext_create_control (dev, group,
					       codec | ports, envy24_set_tdif,
					       typ,
					       tmp, range,
					       MIXF_READABLE |
					       MIXF_WRITEABLE)) < 0)
	    return err;
	}
      else
	{
	  if (i == 8)
	    strcpy (tmp, "ENVY24_SPDOUTL");
	  else if (i == 9)
	    strcpy (tmp, "ENVY24_SPDOUTR");
	  else
	    sprintf (tmp, "ENVY24_OUT%d", i + 1);

	  codec = (i > 1) ? AKM_B : AKM_A;
	  ports = (i & 1) ? 0x08 : 0x04;
	  if ((err = mixer_ext_create_control (dev, group,
					       codec | ports, envy24_set_tdif,
					       typ,
					       tmp, range,
					       MIXF_READABLE |
					       MIXF_WRITEABLE)) < 0)
	    return err;
	}
    }

  return 0;
}

static void
set_tdif_speed (envy24_devc * devc)
{
  int tmp;

  WriteExternRegBit (devc, FPGA_MUTE, 1);	/* Mute FPGA */
  /* Set the TDIF sampling rate */
  devc->gpio_tmp &= ~0x03;
  switch (devc->speed)
    {
    case 32000:
      devc->gpio_tmp |= 0x01;
      break;
    case 44100:
      devc->gpio_tmp |= 0x02;
      break;
    case 48000:
      devc->gpio_tmp |= 0x01;
      break;
    default:
      devc->gpio_tmp |= 0x01;
      break;
    }
  WriteExternReg (devc, devc->gpio_tmp);

  tmp = devc->speedbits;

  switch (devc->syncsource)
    {
    case SYNC_INTERNAL:
      OUTB (devc->osdev, tmp, devc->mt_base + 0x01);
      WriteGPIObit (devc, FPGA_MASTER, 1);
      write_tdif_spdif_reg (devc, 4, read_tdif_spdif_reg (devc, 4) & (~BIT0));
      WriteExternRegBit (devc, 4, 1);	/* Disable TDIF master PLL */
      WriteExternRegBit (devc, 6, 0);	/* Don't route PLL master clock to Envy24. */
      break;

    case SYNC_SPDIF:
      tmp |= 0x10;
      OUTB (devc->osdev, tmp, devc->mt_base + 0x01);
      WriteGPIObit (devc, FPGA_MASTER, 1);
      write_tdif_spdif_reg (devc, 4, read_tdif_spdif_reg (devc, 4) | BIT0);
      WriteExternRegBit (devc, 6, 1);	/* Do route PLL master clock to Envy24. */
      WriteExternRegBit (devc, 4, 1);	/* Disable TDIF master PLL */
      break;

    case SYNC_WCLOCK:
      tmp |= 0x10;
      OUTB (devc->osdev, tmp, devc->mt_base + 0x01);
      write_tdif_spdif_reg (devc, 4, read_tdif_spdif_reg (devc, 4) & (~BIT0));
      WriteExternRegBit (devc, 4, 0);	/* Enable TDIF master PLL */
      WriteExternRegBit (devc, 6, 1);	/* Do route PLL master clock to Envy24. */
      WriteGPIObit (devc, FPGA_MASTER, 0);
      break;
    }

  if (devc->speed > 48000)
    {
      write_tdif_codec (devc, 0x01, 0x00);
      write_tdif_codec (devc, 0x02, 0x65);
      write_tdif_codec (devc, 0x01, 0x03);
    }
  else
    {
      write_tdif_codec (devc, 0x01, 0x00);
      write_tdif_codec (devc, 0x02, 0x60);
      write_tdif_codec (devc, 0x01, 0x03);
    }
  WriteExternRegBit (devc, FPGA_MUTE, 0);	/* Unmute */
}

static void
write_tdif_spdif_reg (envy24_devc * devc, int bRegister, int bData)
{
  unsigned char bMask;
  unsigned char bSPI;

  /* Assert the CODEC chip select and wait at least 150 nS. */
  /* */
  WriteExternRegBit (devc, CS8_CS, CS_TRUE);

  /* Write the SPI address/cmd byte. */
  /* */
  bSPI = CS8_ADDR | CS8_WR;
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop SPI clock low. */
      WriteGPIObit (devc, CS8_CLK, 0);

      /* Write current data bit. */
      if (bMask & bSPI)
	WriteGPIObit (devc, CS8_DOUT, 1);
      else
	WriteGPIObit (devc, CS8_DOUT, 0);

      /* Raise SPI clock to "clock data in". */
      WriteGPIObit (devc, CS8_CLK, 1);
    }

  /* Write the address (MAP) byte. */
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop SPI clock low. */
      WriteGPIObit (devc, CS8_CLK, 0);

      /* Write current data bit. */
      if (bMask & bRegister)
	WriteGPIObit (devc, CS8_DOUT, 1);
      else
	WriteGPIObit (devc, CS8_DOUT, 0);

      /* Raise SPI clock to "clock data in". */
      WriteGPIObit (devc, CS8_CLK, 1);
    }


  /* Write the data byte. */
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop SPI clock low. */
      WriteGPIObit (devc, CS8_CLK, 0);

      /* Write current data bit. */
      if (bMask & bData)
	WriteGPIObit (devc, CS8_DOUT, 1);
      else
	WriteGPIObit (devc, CS8_DOUT, 0);

      /* Raise SPI clock to "clock data in". */
      WriteGPIObit (devc, CS8_CLK, 1);
    }

  /* De-assert chip select. */
  /* */
  WriteExternRegBit (devc, CS8_CS, CS_FALSE);
}

static int
read_tdif_spdif_reg (envy24_devc * devc, int reg)
{
  unsigned char bMask;
  unsigned char bRet = 0;
  unsigned char bSPI;


	/****** WRITE MAP ADDRESS FIRST ******/

  /* Drop the chip select low. */
  /* Wait at least 150 nS. */
  /* */
  WriteExternRegBit (devc, CS8_CS, CS_TRUE);

  /* Write the SPI address/cmd byte. */
  /* */
  bSPI = CS8_ADDR + CS8_WR;	/* SPI address field plus WRITE operation. */
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop clock (GPIO5) low. */
      WriteGPIObit (devc, CDC_CLK, 0);

      /* Write current data bit. */
      if (bMask & bSPI)
	WriteGPIObit (devc, CDC_DOUT, 1);
      else
	WriteGPIObit (devc, CDC_DOUT, 0);

      /* Raise clock (GPIO5). */
      WriteGPIObit (devc, CDC_CLK, 1);
    }


  /* Write the address (MAP) byte. */
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop clock (GPIO5) low. */
      WriteGPIObit (devc, CDC_CLK, 0);

      /* Write current data bit. */
      if (bMask & reg)
	WriteGPIObit (devc, CDC_DOUT, 1);
      else
	WriteGPIObit (devc, CDC_DOUT, 0);

      /* Raise clock (GPIO5). */
      WriteGPIObit (devc, CDC_CLK, 1);
    }

  /* De-assert chip select(s). */
  /* */
  WriteExternRegBit (devc, CS8_CS, CS_FALSE);


	/****** NOW READ THE DATA ******/

  /* Drop the chip select low. */
  /* Wait at least 150 nS. */
  /* */
  WriteExternRegBit (devc, CS8_CS, CS_TRUE);


  /* Write the SPI address/cmd byte. */
  /* */
  bSPI = CS8_ADDR + CS8_RD;	/* SPI address field plus READ operation. */
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop clock (GPIO5) low. */
      WriteGPIObit (devc, CDC_CLK, 0);

      /* Write current data bit. */
      if (bMask & bSPI)
	WriteGPIObit (devc, CDC_DOUT, 1);
      else
	WriteGPIObit (devc, CDC_DOUT, 0);

      /* Raise clock (GPIO5). */
      WriteGPIObit (devc, CDC_CLK, 1);
    }


  /* Read the data byte. */
  /* */
  bRet = 0;
  /* */
  for (bMask = 0x80; bMask; bMask = (bMask >> 1) & 0x7F)
    {
      /* Drop clock low. */
      WriteGPIObit (devc, CDC_CLK, 0);

      /* Read current data bit. */
      if (ReadGPIObit (devc, CDC_DIN))
	bRet |= bMask;

      /* Raise clock. */
      WriteGPIObit (devc, CDC_CLK, 1);
    }


  /* De-assert chip selects. */
  /* */
  WriteExternRegBit (devc, CS8_CS, CS_FALSE);

  /* Return value. */

  return bRet;
}

static int
tdif_get_locked_status (envy24_devc * devc)
{
  switch (devc->syncsource)
    {
    case SYNC_INTERNAL:
      return 1;
      break;

    case SYNC_SPDIF:
      return !(read_tdif_spdif_reg (devc, 16) & 0x17);
      break;

    case SYNC_WCLOCK:
      /* The SPI input pin shows the TDIF locked status */
      if (envy24_read_cci (devc, 0x20) & (1 << 2))
	return 1;
      return 0;
      break;
    }

  return 1;
}

envy24_auxdrv_t tdif_auxdrv = {
  tdif_card_init,
  tdif_mix_init,
  init_cs8427_spdif,
  write_cs8427_spdif,
  cs8427_spdif_ioctl,
  read_tdif_spdif_reg,
  write_tdif_spdif_reg,
  set_tdif_speed,
  tdif_get_locked_status,
  cs8427_spdif_mixer_init,
  tdif_card_uninit
};
