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

/* PCF8575 pin assignment  (default 0xffff) */
#define PCF_SPDIN_SELECT	0x0001	/* 0=optical 1=coax */
#define PCF_OPTOUT		0x0002	/* 0=SPDIF 1=ADAT */
#define PCF_CLOCKSRC		0x0004	/* 0=ADAT 1=SPDIF */
#define PCF_ADATEN		0x0008	/* 0=ADAT disabled 1=ADAT enabled */
#define PCF_ADATLOOP_		0x0010	/* 0=ADAT in->out loop, 1=no loop */
#define PCF_CS8414_ERF_		0x0020	/* Inverted CS8414 ERF */
#define PCF_SPDIF_ERR		0x0040	/* OR'ed from CS8414 E0..E2 */
#define PCF_ADAT_ERROR		0x0080	/* ADAT error input */

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

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

  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 __inline__ void
IIC_SetIic (envy24_devc * devc, unsigned int dwPortAddr, 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;
/*	bReg = 0; */
  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       : 
=============================================================================*/
static __inline__ void
IIC_Start (envy24_devc * devc, unsigned int dwPortAddr)
{
  /* falling edge of SDA while SCL is HIGH */
  IIC_SetIic (devc, dwPortAddr, 1, 1);
  IIC_SetIic (devc, dwPortAddr, 0, 1);
}

/*=============================================================================
  Function    : IIC_Stop  
-------------------------------------------------------------------------------
  Description : 
  Returns     : void -> 
  Parameters  : unsigned int dwPortAddr -> 
-------------------------------------------------------------------------------
  Notes       : 
=============================================================================*/
static __inline__ void
IIC_Stop (envy24_devc * devc, unsigned int dwPortAddr)
{
  /* rising edge of SDA while SCL is HIGH */
  IIC_SetIic (devc, dwPortAddr, 0, 1);
  IIC_SetIic (devc, dwPortAddr, 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 int dwPortAddr,
	      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, dwPortAddr, bDataBit, 0);
      IIC_SetIic (devc, dwPortAddr, bDataBit, 1);
    }				/* end for i */

  IIC_SetIic (devc, dwPortAddr, 1, 0);

  /* Get acknowledge */
  IIC_SetIic (devc, dwPortAddr, 1, 1);
  bAck = IIC_GetSDA (devc, dwPortAddr);
  /* FMB this is a start condition but never mind */
  IIC_SetIic (devc, dwPortAddr, 0, 0);
  return 1;
  /* return (!bAck); *//* bAck = 0 --> success */
}

static unsigned char
IIC_WriteWord (envy24_devc * devc, int dwPortAddr, unsigned char bIicAddress,
	       unsigned short bByte)
{
  IIC_Start (devc, dwPortAddr);

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

  IIC_Stop (devc, dwPortAddr);
  return 1;

FAILED:
  IIC_Stop (devc, dwPortAddr);
  return 0;
}

static void
ews88d_set_pcf8575 (envy24_devc * devc, unsigned short d)
{
  if (!IIC_WriteWord (devc, devc->ccs_base, 0x40, d))
    {
      cmn_err (CE_CONT, "IIC_WriteWord failed\n");
    }
  devc->gpio_tmp = d;
}

static void
ews88d_card_init (envy24_devc * devc)
{
  ews88d_set_pcf8575 (devc, 0xffff);
}

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

  tmp = devc->speedbits;

  switch (devc->syncsource)
    {
    case SYNC_INTERNAL:
      OUTB (devc->osdev, tmp, devc->mt_base + 0x01);
      break;

    case SYNC_SPDIF:
      tmp |= 0x10;
      OUTB (devc->osdev, tmp, devc->mt_base + 0x01);
      devc->gpio_tmp &= ~PCF_CLOCKSRC;
      ews88d_set_pcf8575 (devc, devc->gpio_tmp);
      break;

    case SYNC_WCLOCK:
      tmp |= 0x10;
      OUTB (devc->osdev, tmp, devc->mt_base + 0x01);
      devc->gpio_tmp |= PCF_CLOCKSRC;
      ews88d_set_pcf8575 (devc, devc->gpio_tmp);
      break;
    }

  if (devc->speed > 48000)
    {
      devc->gpio_tmp &= ~PCF_ADATEN;
      ews88d_set_pcf8575 (devc, devc->gpio_tmp);
    }
  else
    {
      devc->gpio_tmp |= PCF_ADATEN;
      ews88d_set_pcf8575 (devc, devc->gpio_tmp);
    }
}

static int
ews88d_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:
	return !!(devc->gpio_tmp & PCF_SPDIN_SELECT);
	break;

      case 2:
	return !!(devc->gpio_tmp & PCF_OPTOUT);
	break;

      case 3:
	return !(devc->gpio_tmp & PCF_ADATLOOP_);
	break;
      }

  if (cmd == SNDCTL_MIX_WRITE)
    switch (ctrl)
      {
      case 1:
	devc->gpio_tmp &= ~PCF_SPDIN_SELECT;
	if (value)
	  devc->gpio_tmp |= PCF_SPDIN_SELECT;
	ews88d_set_pcf8575 (devc, devc->gpio_tmp);
	return !!(devc->gpio_tmp & PCF_SPDIN_SELECT);
	break;

      case 2:
	devc->gpio_tmp &= ~PCF_OPTOUT;
	if (value)
	  devc->gpio_tmp |= PCF_OPTOUT;
	ews88d_set_pcf8575 (devc, devc->gpio_tmp);
	return !!(devc->gpio_tmp & PCF_OPTOUT);
	break;

      case 3:
	devc->gpio_tmp &= ~PCF_ADATLOOP_;
	if (!value)
	  devc->gpio_tmp |= PCF_ADATLOOP_;
	ews88d_set_pcf8575 (devc, devc->gpio_tmp);
	return !(devc->gpio_tmp & PCF_ADATLOOP_);
	break;
      }

  return -EINVAL;
}

static int
ews88d_mix_init (envy24_devc * devc, int dev, int group)
{
  int err;

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

  if ((err = mixer_ext_create_control (dev, group,
				       1, ews88d_mixer_set,
				       MIXT_ENUM,
				       "EWS88D_SPDIN", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       2, ews88d_mixer_set,
				       MIXT_ENUM,
				       "EWS88D_OPTOUT", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

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

  return 0;
}

envy24_auxdrv_t ews88d_auxdrv = {
  ews88d_card_init,
  ews88d_mix_init,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  set_ews88d_speed,
  NULL,
  cs8427_spdif_mixer_init
};
