/*
 * Purpose: Card specific routines for Terratec DMX6fire.
 */
/*
 * 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"

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

#define DEFAULT_FRONTBOX_SETUP	0xfd

extern int envy24_gain_sliders;
extern int envy24_virtualout;

#define EWX_DIG_ADDR 0x24	/* IIC address of digital traceiver chip of 6fire */

/*=============================================================================
  Function    : IIC_GetSDA
-------------------------------------------------------------------------------
  Description : 
  Returns     : unsigned char  -> 
  Parameters  : unsigned long dwPortAddr -> 
-------------------------------------------------------------------------------
  Notes       : 
=============================================================================*/
static unsigned char
IIC_GetSDA (envy24_devc * devc)
{

  unsigned char bReg, bSDA;

  /* FMB TEST: RW line */
  /* set write mask */
  bReg = ~(0x08);		/* writeable */
  envy24_write_cci (devc, 0x21, bReg);

  /* set RW line LOW */
  bReg = 0;			/* writeable */
  envy24_write_cci (devc, 0x20, bReg);

  /* Set direction: SDA to input and SCL to output */
  bReg = envy24_read_cci (devc, 0x22);
  bReg |= 0x20;			/* SCL output = 1 */
  bReg &= ~0x10;		/* SDA input  = 0 */
  envy24_write_cci (devc, 0x22, bReg);

  /* Get SDA line state */
  bSDA = envy24_read_cci (devc, 0x20);


  /* set RW line HIGH */
  bReg = 0x08;			/* writeable */
  envy24_write_cci (devc, 0x20, bReg);

  return 1;
  /* return ((bSDA & 0x10) == 0x10); */

}

/*=============================================================================
  Function    : IIC_SetIic
-------------------------------------------------------------------------------
  Description : 
  Returns     : void  -> 
  Parameters  : unsigned int dwPortAddr -> 
              : unsigned char fSDA -> 
              : unsigned char fSCL -> 
-------------------------------------------------------------------------------
  Notes       : 
=============================================================================*/
static void
IIC_SetIic (envy24_devc * devc, unsigned char fSDA, unsigned char fSCL)
{
  unsigned char bReg;

  /* Set direction: SDA and SCL to output */
  bReg = envy24_read_cci (devc, 0x22);
  bReg |= (0x20 | 0x10);	/* 1 -> output */
  envy24_write_cci (devc, 0x22, bReg);

  /* set write mask */
  bReg = ~(0x20 | 0x10);	/* writeable */
  envy24_write_cci (devc, 0x21, bReg);

  /* Set line state */
  /* FMB TEST: RW line */
  bReg = 0x08;
  if (fSDA)
    bReg += 0x10;
  if (fSCL)
    bReg += 0x20;
  envy24_write_cci (devc, 0x20, bReg);

}

/*=============================================================================
  Function    : IIC_Start
-------------------------------------------------------------------------------
  Description : 
  Returns     : void  -> 
  Parameters  : unsigned int dwPortAddr -> 
-------------------------------------------------------------------------------
  Notes       : 
=============================================================================*/
/*__inline  */
static void
IIC_Start (envy24_devc * devc)
{
  /* falling edge of SDA while SCL is HIGH */
  IIC_SetIic (devc, 1, 1);
  oss_udelay (30);
  IIC_SetIic (devc, 0, 1);
}

/*=============================================================================
  Function    : IIC_Stop  
-------------------------------------------------------------------------------
  Description : 
  Returns     : void -> 
  Parameters  : unsigned int dwPortAddr -> 
-------------------------------------------------------------------------------
  Notes       : 
=============================================================================*/
/*__inline  */

static void
IIC_Stop (envy24_devc * devc)
{
  /* rising edge of SDA while SCL is HIGH */
  IIC_SetIic (devc, 0, 1);
  IIC_SetIic (devc, 1, 1);
}


/*=============================================================================
  Function    : IIC_SendByte
-------------------------------------------------------------------------------
  Description : 
  Returns     : unsigned char -> 
  Parameters  : unsigned int dwPortAddr -> 
              : unsigned char bByte -> 
-------------------------------------------------------------------------------
  Notes       : 
=============================================================================*/
/*__inline  */
static unsigned char
IIC_SendByte (envy24_devc * devc, unsigned char bByte)
{
  unsigned char bDataBit, bAck;
  int i;

  for (i = 7; i >= 0; i--)	/* send byte (MSB first) */
    {
      bDataBit = (bByte >> i) & 0x01;

      IIC_SetIic (devc, bDataBit, 0);
      IIC_SetIic (devc, bDataBit, 1);
      IIC_SetIic (devc, bDataBit, 0);
    }				/* end for i */

  IIC_SetIic (devc, 1, 0);

  /* Get acknowledge */
  IIC_SetIic (devc, 1, 1);
  bAck = IIC_GetSDA (devc);
  /* FMB this is a start condition but never mind */
  IIC_SetIic (devc, 0, 0);
#if 0
  if (bAck)
    cmn_err (CE_CONT, "SendByte failed %02x\n", bByte);
  else
    cmn_err (CE_CONT, "SendByte OK %02x\n", bByte);
#endif
  return 1;
  /* return (!bAck); *//* bAck = 0 --> success */
}

#if 0
/*=============================================================================
  Function    : IIC_WriteByte
-------------------------------------------------------------------------------
  Description : 
  Returns     : unsigned char  -> 
  Parameters  : unsigned int dwPortAddr -> 
              : unsigned char bIicAddress -> 
              : unsigned char bByte -> 
-------------------------------------------------------------------------------
  Notes       : 
=============================================================================*/
static unsigned char
IIC_WriteByte (envy24_devc * devc, unsigned char bIicAddress,
	       unsigned char bByte)
{
  IIC_Start (devc);

  /* send IIC address and data byte */
  if (!IIC_SendByte (devc, bIicAddress))
    {
      cmn_err (CE_CONT, "IIC_SendByte 1 failed\n");
      goto FAILED;
    }
  if (!IIC_SendByte (devc, bByte))
    {
      cmn_err (CE_CONT, "IIC_SendByte 2 failed\n");
      goto FAILED;
    }

  IIC_Stop (devc);
  return 1;

FAILED:
  IIC_Stop (devc);
  return 0;
}
#endif

static unsigned char
IIC_WriteWord (envy24_devc * devc, unsigned char bIicAddress,
	       unsigned char reg, unsigned char bByte)
{
  IIC_Start (devc);

  /* send IIC address and data byte */
  if (!IIC_SendByte (devc, bIicAddress))
    {
      cmn_err (CE_CONT, "IIC_SendByte 1 failed\n");
      goto FAILED;
    }
  if (!IIC_SendByte (devc, reg))
    {
      cmn_err (CE_CONT, "IIC_SendByte 2 failed\n");
      goto FAILED;
    }
  if (!IIC_SendByte (devc, bByte))
    {
      cmn_err (CE_CONT, "IIC_SendByte 3 failed\n");
      goto FAILED;
    }

  IIC_Stop (devc);
  return 1;

FAILED:
  IIC_Stop (devc);
  return 0;
}

static void
FrontboxControl (envy24_devc * devc, int b)
{
  IIC_WriteWord (devc, 0x40, 0x01, b);	/* Output register */
  devc->gpio_tmp = b;
}

static int
HW_AK4525_SetChipSelect (envy24_devc * devc, unsigned char bCsMask)
{
  unsigned char b;
  /* check validity */
  if (bCsMask > 0x07)
    {
      cmn_err (CE_CONT, "Invalid bCsMask %02x\n", bCsMask);
      return 0;			/* invalid  */
    }

  envy24_write_cci (devc, 0x21, 0x00);
  envy24_write_cci (devc, 0x22, 0xff);
  b = envy24_read_cci (devc, 0x20);
  b &= ~0x07;

  b |= bCsMask & 0x07;

  envy24_write_cci (devc, 0x20, b);

  return 1;
}

#define HW_AK4525_SetLines IIC_SetIic

/*=============================================================================
    FUNCTION:   HW_AK4525_WriteRegister 
-------------------------------------------------------------------------------
	PURPOSE:    
    PARAMETERS: unsigned int port      - port address for IIC port
                unsigned char bCsMask   - CoDec bitmask (bits 0..3)
		                  or 0xFF for all codecs together
		unsigned char bRegIdx   - offset to desired register
                unsigned char bReg      - new register value
	RETURNS:    
-------------------------------------------------------------------------------
	NOTES:      The Chip Selects must have been set prior to this call
				PCF8574_CODEC_IIC_ADDRESS    

	+-----------------------------------------------------------------+
	| B15 B14 B13 B12 B11 B10 B09 B08|B07 B06 B05 B04 B03 B02 B01 B00 |
	+--------+---+-------------------+--------------------------------+
	| C1  C0 |R/W|A4  A3  A2  A1  A0 |D7  D6  D5  D4  D3  D2  D1  D0  |
	| 1   0  |1  |        <= 7       |           data                 |
	+--------+---+-------------------+--------------------------------+

=============================================================================*/
static unsigned char
HW_AK4525_WriteRegister (envy24_devc * devc, unsigned char bCsMask,
			 unsigned char bRegIdx, unsigned char bRegData)
{

  unsigned short wCmd;
  unsigned char i;
  unsigned char fData;

  /* format buffer */
  wCmd = 0xA000 +		/* chip address + R/W */
    (((unsigned short) bRegIdx) << 8) +	/* register address */
    bRegData;

  HW_AK4525_SetLines (devc, 0, 0);

  /* start write cycle */
  if (!HW_AK4525_SetChipSelect (devc, bCsMask))
    {
      cmn_err (CE_CONT, "HW_AK4525_SetChipSelect failed\n");
      return 0;			/* = CS to LOW -> data latched */
    }


  for (i = 0; i < 16; i++)
    {
      fData = (wCmd & 0x8000) ? 1 : 0;
      HW_AK4525_SetLines (devc, fData, 0);
      HW_AK4525_SetLines (devc, fData, 1);	/* data is clocked in on rising edge of CCLK */
      HW_AK4525_SetLines (devc, fData, 0);
      wCmd <<= 1;
    }

  /* leave data line HIGH (= default for IIC) */
  HW_AK4525_SetLines (devc, fData, 0);	/* data is clocked in on rising edge of CCLK */

  /* end write cycle */
  if (!HW_AK4525_SetChipSelect (devc, 0x00))
    {
      cmn_err (CE_CONT, "HW_AK4525_SetChipSelect 2 failed\n");
      return 0;			/* = all CS to HIGH -> CS to default */
    }

  /* default */
  HW_AK4525_SetLines (devc, 1, 1);	/* data is clocked in on rising edge of CCLK */

  return 1;
}


/* Register offsets */
enum
{
  AK4525_REG_POWER,
  AK4525_REG_RESET,
  AK4525_REG_FORMAT,
  AK4525_REG_DEEMPHASIS,
  AK4525_REG_LEFT_INPUT,
  AK4525_REG_RIGHT_INPUT,
  AK4525_REG_LEFT_OUTPUT,
  AK4525_REG_RIGHT_OUTPUT
};


/*=============================================================================
  Function    : HW_AK4525_Reset
-------------------------------------------------------------------------------
  Description : 
  Returns     : unsigned char 
  Parameters  : PCUST_HW_INSTANCE_DATA  devc - 
                int nCodecIdx -> 0..3 index of Codec ,-1 -> all
-------------------------------------------------------------------------------
  Notes       : 
=============================================================================*/
static unsigned char
HW_AK4525_Reset (envy24_devc * devc, int nCodecIdx)
{
  unsigned char bCodecMask = 0;
  if (nCodecIdx == -1)
    bCodecMask = 0x07;
  else
    bCodecMask = 0x01 << (nCodecIdx);

  if (!HW_AK4525_WriteRegister (devc, bCodecMask,	/* 0x0F -> set mode for all codecs */
				AK4525_REG_RESET, 0x00))	/* DACs,ADCs -> reset */
    {
      cmn_err (CE_CONT, "REG_RESET failed\n");
      return 0;
    }

  if (!HW_AK4525_WriteRegister (devc, bCodecMask,	/* 0x0F -> set mode for all codecs */
				AK4525_REG_FORMAT, 0x60))	/* IIS, 256 fsn, normal speed */
    return 0;

  if (!HW_AK4525_WriteRegister (devc, bCodecMask,	/* 0x0F -> set mode for all codecs */
				AK4525_REG_RESET, 0x03))	/* DACs,ADCs -> normal operation */
    return 0;

  if (!HW_AK4525_WriteRegister (devc, bCodecMask,	/* 0x0F -> set mode for all codecs */
				AK4525_REG_POWER, 0x07))	/* power on */
    return 0;

  if (!HW_AK4525_WriteRegister (devc, bCodecMask,	/* soft mute timeout --> short */
				AK4525_REG_DEEMPHASIS, 0x19))
    return 0;

  if (!HW_AK4525_WriteRegister (devc,
				bCodecMask, AK4525_REG_LEFT_INPUT, 0x7f))
    return 0;

  if (!HW_AK4525_WriteRegister (devc,
				bCodecMask, AK4525_REG_RIGHT_INPUT, 0x7f))
    return 0;

  if (!HW_AK4525_WriteRegister (devc,
				bCodecMask, AK4525_REG_LEFT_OUTPUT, 0x7f))
    return 0;

  if (!HW_AK4525_WriteRegister (devc,
				bCodecMask, AK4525_REG_RIGHT_OUTPUT, 0x7f))
    return 0;

  return 1;
}

static void
set_dmx6fire_speed (envy24_devc * devc)
{
  unsigned char tmp;

  tmp = devc->speedbits;
  if (devc->syncsource != SYNC_INTERNAL)
    {
      tmp |= 0x10;		/* S/PDIF input clock select */
      if (devc->model_data->flags & MF_WCLOCK)	/* Has world clock too */
	{
	  int cmd = envy24_read_cci (devc, 0x20);
	  cmd |= 0x10;		/* S/PDIF */
	  if (devc->syncsource == SYNC_WCLOCK)
	    cmd &= ~0x10;	/* World clock */
	  envy24_write_cci (devc, 0x20, cmd);
	}
    }
  OUTB (devc->osdev, tmp, devc->mt_base + 0x01);
}

#if 0
/*
 * S/PDIF register access doesn't work so this feature is not enabled.
 */

#define EWX_CLK 0x20
#define EWX_DTA 0x10

static void
dmx6fire_iic_write_byte (envy24_devc * devc, unsigned char data,
			 unsigned char save)
{
  int i;
  unsigned char gpio;

  for (i = 0; i < 8; i++)
    {
      int fData = (data & 0x80) ? 1 : 0;

      gpio = 0x08;
      if (fData)
	gpio |= EWX_DTA;	/* DATA */
      envy24_write_cci (devc, 0x20, gpio | save);
      envy24_write_cci (devc, 0x20, gpio | EWX_CLK | save);	/*  Clock pulse */
      envy24_write_cci (devc, 0x20, gpio | save);

      data <<= 1;
    }

  envy24_write_cci (devc, 0x20, EWX_DTA | save);
  envy24_write_cci (devc, 0x20, EWX_DTA | EWX_CLK | save);	/*  Clock pulse  (ACK) */
  envy24_write_cci (devc, 0x20, EWX_DTA | save);
}

static unsigned char
dmx6fire_iic_read_byte (envy24_devc * devc, unsigned char save)
{
  int i;
  unsigned char data = 0;
  int b;

  PRT_STATUS (0x01);
  save |= EWX_DTA;
  envy24_write_cci (devc, 0x20, save);
  oss_udelay (10);
#if 1
  save &= ~8;			/* R/W bit */
  envy24_write_cci (devc, 0x22, ~EWX_DTA);	/* GPIO direction */
  envy24_write_cci (devc, 0x21, EWX_DTA);	/* GPIO write mask */
#endif

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

      envy24_write_cci (devc, 0x20, EWX_CLK | save);	/*  Clock pulse */
      b = envy24_read_cci (devc, 0x20);
      if (b & EWX_DTA)
	data |= 0x80;
      envy24_write_cci (devc, 0x20, save);

      data >>= 1;
    }

  envy24_write_cci (devc, 0x22, 0xff);	/* GPIO direction */
  envy24_write_cci (devc, 0x21, 0x00);	/* GPIO write mask */
  envy24_write_cci (devc, 0x20, (save & ~EWX_DTA) | 0x08);	/* Low for the ACK bit */
  envy24_write_cci (devc, 0x20, (save & ~EWX_DTA) | 0x08 | EWX_CLK);	/* Clock for the ACK bit */
  envy24_write_cci (devc, 0x20, save | 0x08);
  oss_udelay (10);
  return data;
}

#if 0
static void
dmx6fire_iic_write (envy24_devc * devc, int addr,
		    unsigned char bRegIdx, unsigned char bRegData)
{
  unsigned char save;

  /* start write cycle */
  envy24_write_cci (devc, 0x22, 0xff);	/* GPIO direction */
  envy24_write_cci (devc, 0x21, 0x00);	/* GPIO write mask */

  save = (envy24_read_cci (devc, 0x20) & ~(EWX_CLK | EWX_DTA)) | 0x08	/* R/W bit */
    ;
  /* Send start */
  envy24_write_cci (devc, 0x20, save | EWX_CLK | EWX_DTA);
  envy24_write_cci (devc, 0x20, save | EWX_CLK);
  envy24_write_cci (devc, 0x20, save);

  dmx6fire_iic_write_byte (devc, addr, save);
  dmx6fire_iic_write_byte (devc, bRegIdx, save);
  dmx6fire_iic_write_byte (devc, bRegData, save);

  /* Send stop */
  envy24_write_cci (devc, 0x20, save);
  envy24_write_cci (devc, 0x20, save | EWX_CLK);
  envy24_write_cci (devc, 0x20, save | EWX_CLK | EWX_DTA);
}
#endif

static unsigned char
dmx6fire_iic_read (envy24_devc * devc, int addr, unsigned char bRegIdx)
{
  unsigned char save, data;

  /* dmx6fire_iic_write(devc, addr, bRegIdx, 0x55); */
  PRT_STATUS (0x80);

  envy24_write_cci (devc, 0x22, 0xff);	/* GPIO direction */
  envy24_write_cci (devc, 0x21, 0x00);	/* GPIO write mask */

  save = (envy24_read_cci (devc, 0x20) & ~(EWX_DTA | EWX_CLK)) | 0x08	/* R/W bit */
    ;
  PRT_STATUS (0x02);
  /* Send start */
  envy24_write_cci (devc, 0x20, save | EWX_CLK | EWX_DTA);
  envy24_write_cci (devc, 0x20, save | EWX_CLK);
  envy24_write_cci (devc, 0x20, save);

  dmx6fire_iic_write_byte (devc, addr | 0x01, save);
  data = dmx6fire_iic_read_byte (devc, save);

  /* Send stop */
  envy24_write_cci (devc, 0x20, save);
  envy24_write_cci (devc, 0x20, save | EWX_CLK);
  envy24_write_cci (devc, 0x20, save | EWX_CLK | EWX_DTA);
  PRT_STATUS (0x00);
  return data;
}

static int
read_dmx6fire_spdif_reg (envy24_devc * devc, int bRegister)
/*
*****************************************************************************
*  Reads a byte from a specific CS8427 register.
****************************************************************************/
{

  unsigned char ret;
  ret = dmx6fire_iic_read (devc, EWX_DIG_ADDR, bRegister);

  return ret;
}

static void
write_dmx6fire_spdif_reg (envy24_devc * devc, int bRegister, int bData)
{

  /*dmx6fire_iic_write(devc, EWX_DIG_ADDR, bRegister, bData); */
  IIC_WriteWord (devc, EWX_DIG_ADDR, bRegister, bData);
}
#else
static int
read_dmx6fire_spdif_reg (envy24_devc * devc, int bRegister)
{
  return 0;
}

static void
write_dmx6fire_spdif_reg (envy24_devc * devc, int bRegister, int bData)
{
}
#endif

static void
dmx6fire_card_init (envy24_devc * devc)
{
  envy24_write_cci (devc, 0x20, 0xff);
  envy24_write_cci (devc, 0x21, 0x00);
  envy24_write_cci (devc, 0x22, 0xff);
  envy24_write_cci (devc, 0x20, 0xff);
  HW_AK4525_SetChipSelect (devc, 0x00);
#if 1

  IIC_WriteWord (devc, 0x40, 0x02, 0x00);	/* Polarity register */
  IIC_WriteWord (devc, 0x40, 0x03, 0x00);	/* Direction register */

  FrontboxControl (devc, DEFAULT_FRONTBOX_SETUP);	/* Set default values */

  if (!HW_AK4525_Reset (devc, -1))
    cmn_err (CE_CONT, "Envy24: DMX6fire reset failed\n");;
#endif

}

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

  if (cmd == SNDCTL_MIX_READ)
    switch (ctrl)
      {
      case 1:
	if (devc->skipdevs == 2)
	  return devc->rec_portc[2].riaa_filter;
	else
	  return devc->rec_portc[4].riaa_filter;
	break;

      }

  if (cmd == SNDCTL_MIX_WRITE)
    switch (ctrl)
      {
      case 1:
	if (devc->skipdevs == 2)
	  return devc->rec_portc[2].riaa_filter = !!value;
	else
	  return devc->rec_portc[4].riaa_filter = !!value;
	break;

      }

  return -EINVAL;
}

static int
dmx6fire_mix_init (envy24_devc * devc, int dev, int group)
{
  int err;
#if 0
  int i, mask = devc->outportmask, skip, codec, ports;
  int range = 3;
  int typ = MIXT_ENUM;
  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)
    {
      char tmp[32];

      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;
	}
    }
#endif

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

  if ((err = mixer_ext_create_control (dev, group,
				       1, dmx6fire_mixer_set,
				       MIXT_ONOFF,
				       "6FIRE_RIAA", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  return 0;
}

envy24_auxdrv_t dmx6fire_auxdrv = {
  dmx6fire_card_init,
  dmx6fire_mix_init,
  init_cs8427_spdif,
  write_cs8427_spdif,
  NULL,
  read_dmx6fire_spdif_reg,
  write_dmx6fire_spdif_reg,
  set_dmx6fire_speed,
  NULL,
  cs8427_spdif_mixer_init
};
