/*
 * Purpose: Low level routines for the ESI (Egosys) Juli@ card
 */
/*
 * 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"

#define AK4358_ADDRESS 0x11
#define AK4114_ADDRESS 0x10

static unsigned char
i2c_read (envy24ht_devc * devc, unsigned char addr, unsigned char pos)
{
  int i;
  unsigned char data;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  OUTB (devc->osdev, pos, devc->ccs_base + 0x11);	/* Offset */
  OUTB (devc->osdev, addr << 1, devc->ccs_base + 0x10);	/* Read address  */

  for (i = 0; i < 2000; i++)
    {
      unsigned char status = INB (devc->osdev, devc->ccs_base + 0x13);
      if (!(status & 1))
	break;

    }

  oss_udelay (1);
  data = INB (devc->osdev, devc->ccs_base + 0x12);
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  return data;
}

static void
i2c_write (envy24ht_devc * devc, unsigned char addr, unsigned char pos,
	   unsigned char data)
{
  int i;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

  for (i = 0; i < 2000; i++)
    {
      unsigned char status = INB (devc->osdev, devc->ccs_base + 0x13);
      if (!(status & 1))
	break;

    }

  OUTB (devc->osdev, pos, devc->ccs_base + 0x11);	/* Offset */
  OUTB (devc->osdev, data, devc->ccs_base + 0x12);	/* Data */
  OUTB (devc->osdev, (addr << 1) | 1, devc->ccs_base + 0x10);	/* Write address  */

  for (i = 0; i < 2000; i++)
    {
      unsigned char status = INB (devc->osdev, devc->ccs_base + 0x13);
      if (!(status & 1))
	break;

    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);

  if ((addr == AK4358_ADDRESS) && (pos <= 0x0c))
    devc->m_DACVolume[pos] = data;
}

static void
GPIOWrite (envy24ht_devc * devc, int pos, int bit)
{
  int data = INW (devc->osdev, devc->ccs_base + 0x14);

  bit = (bit != 0);

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

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

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

  i2c_write (devc, AK4358_ADDRESS, reg, level | 0x80);

  return level;
}

static struct { 
    int rate, spdifin, clks;
} rate_sel[] = {
    {32000, 0x30, 0x08},
    {44100, 0x00, 0x09},
    {48000, 0x20, 0x0A},
    {88200, 0x80, 0x05},
    {96000, 0xA0, 0x06},
    {176400, 0xC0, 0x01},
    {192000, 0xE0, 0x02},
    {16000, -1, 0x0C},
    {22050, -1, 0x0D},
    {24000, -1, 0x0E},
    {64000, -1, 0x04},
    {128000, -1, 0x00},
    {-1, -1, -1}
};

static void
julia_set_deemph (envy24ht_devc * devc, int mode)
{
  int deemph = 0x01; /* OFF */

  if (mode == 0) 
    i2c_write (devc, AK4358_ADDRESS, 0x03, deemph);
  else {

    if (devc->speed == 44100)
      deemph = 0x00;
    else if (devc->speed == 48000)
      deemph = 0x02;
    else if (devc->speed == 32000)
      deemph = 0x03;

    i2c_write (devc, AK4358_ADDRESS, 0x03, deemph);
  }
}

static void
julia_Monitor (envy24ht_devc * devc, int bMonitor, int num)
{
  switch (num)
    {
    case 0:	/* MUTE */
	if (bMonitor)
    	  {
	    i2c_write (devc, AK4358_ADDRESS, 1, 0x03);
	    GPIOWrite (devc, 15, 1);
	  } else {
    	    i2c_write (devc, AK4358_ADDRESS, 1, 0x01);
	    GPIOWrite (devc, 15, 0);
	  }
	break;

    case 1:	/* LINEIN */
	if (bMonitor)
    	    GPIOWrite (devc, 13, 1);
	else
    	    GPIOWrite (devc, 13, 0);
	break;

    case 2:	/* SPDIFOUT */
	if (bMonitor)
    	    GPIOWrite (devc, 11, 1);
	else
    	    GPIOWrite (devc, 11, 0);
	break;
    case 3:	/* SPDIFIN */
	if (bMonitor)
    	    GPIOWrite (devc, 12, 1);
	else
    	    GPIOWrite (devc, 12, 0);
	break;
    case 4:	/* De-emphasis */
    	julia_set_deemph (devc, bMonitor);
	break;
    }
  devc->monitor[num] = bMonitor;
}

static void
ak4114_init (envy24ht_devc * devc)
{
  /*
   * AK4114 S/PDIF interface initialization
   */
  i2c_write (devc, AK4114_ADDRESS, 0x00, 0x0f);
  i2c_write (devc, AK4114_ADDRESS, 0x01, 0x70);
  i2c_write (devc, AK4114_ADDRESS, 0x02, 0x80);
  i2c_write (devc, AK4114_ADDRESS, 0x03, 0x49);
  i2c_write (devc, AK4114_ADDRESS, 0x04, 0x00);
  i2c_write (devc, AK4114_ADDRESS, 0x05, 0x00);

  i2c_write (devc, AK4114_ADDRESS, 0x0d, 0x41);
  i2c_write (devc, AK4114_ADDRESS, 0x0e, 0x02);
  i2c_write (devc, AK4114_ADDRESS, 0x0f, 0x2c);
  i2c_write (devc, AK4114_ADDRESS, 0x10, 0x00);
  i2c_write (devc, AK4114_ADDRESS, 0x11, 0x00);
}

static void
julia_set_rate (envy24ht_devc * devc)
{
  int i, data;
  int adc = 0x00;

  if (devc->speed <= 48000)
	devc->m_DACVolume[2] = 0x4f; /* DFS=normal-speed */	
  else if (devc->speed <= 96000)
    {
	adc = 0x04;
	devc->m_DACVolume[2] = 0x5f; /* DFS=double-speed */
    }
  else
    {
	adc = 0x03;
	devc->m_DACVolume[2] = 0x6f; /* DFS=quad-speed */
    }
    
  data = INW (devc->osdev, devc->ccs_base + 0x14);
  data &= ~0x70f;
  data |= adc << 8;

  for (i = 0; rate_sel[i].rate; i++)
    if (rate_sel[i].rate == devc->speed) 
	data |= rate_sel[i].clks;
  
  OUTW (devc->osdev, data, devc->ccs_base + 0x14);

  OUTB (devc->osdev, 0x80, devc->mt_base + 0x05);   /* RESET */
  OUTB (devc->osdev, 0x00, devc->mt_base + 0x05);
  
  if (devc->speed == 8000)
	OUTB (devc->osdev, 0x06, devc->mt_base + 0x01);
  else if (devc->speed == 9600)
	OUTB (devc->osdev, 0x03, devc->mt_base + 0x01);
  else if (devc->speed == 11025)
	OUTB (devc->osdev, 0x0a, devc->mt_base + 0x01);
  else if (devc->speed == 12000)
	OUTB (devc->osdev, 0x02, devc->mt_base + 0x01);
  else
	OUTB (devc->osdev, 0x10, devc->mt_base + 0x01);

    /* Restore ak4358 regs and set DFS */
  for (i = 0; i <= 0x0c; i++)
    i2c_write (devc, AK4358_ADDRESS, i, devc->m_DACVolume[i]);
    /* Restore ak4114 */
  ak4114_init (devc);
  if (devc->monitor[4] == 1)
    julia_set_deemph (devc, 1);
}

static void
julia_sync_ak4114 (void *arg)
{
  envy24ht_devc *devc = arg;
  int i;
  int spdifin = i2c_read (devc, AK4114_ADDRESS, 7);

  for (i = 0; rate_sel[i].rate; i++)
    if (rate_sel[i].spdifin == spdifin)
     {
      if (devc->speed != rate_sel[i].rate);
       {
        devc->speed = rate_sel[i].rate;
        mixer_devs[devc->mixer_dev]->modify_counter++;
        julia_set_rate (devc);
       }
      break;
     }
  
  if (devc->syncsource == 1)
    devc->timeout_id = timeout (julia_sync_ak4114, devc, OSS_HZ);
}

static int
julia_set_syncsource (envy24ht_devc * devc, int value)
{
  if (value == 1)
  {
    GPIOWrite (devc, 4, 0);
    devc->timeout_id = timeout (julia_sync_ak4114, devc, OSS_HZ);
  } else {
    if (devc->timeout_id != 0)
       untimeout (devc->timeout_id);
    devc->timeout_id = 0;

    GPIOWrite (devc, 4, 1);
    
    if (devc->speed != devc->pending_speed)
    {
      devc->speed = devc->pending_speed;
      mixer_devs[devc->mixer_dev]->modify_counter++;
      julia_set_rate (devc);
    }
  }

  return 0;
}

static int
julia_audio_ioctl (envy24ht_devc * devc, envy24ht_portc * portc, int cmd,
		   int *arg)
{
  int left, right, value;

  switch (cmd)
    {
    case SNDCTL_DSP_GETPLAYVOL:
      if (portc != &devc->play_portc[0])
	return OSS_EINVAL;
      left = (devc->gains[0] & 0xff) * 100 / 0x7f;
      right = ((devc->gains[0] >> 8) & 0xff) * 100 / 0x7f;
      return *arg = (left | (right << 8));
      break;

    case SNDCTL_DSP_SETPLAYVOL:
      if (portc != &devc->play_portc[0])
	return OSS_EINVAL;
      value = *arg;
      left = value & 0xff;
      right = (value >> 8) & 0xff;

      left = (left * 0x7f) / 100;
      right = (right * 0x7f) / 100;
      left = set_dac (devc, 0x04, left);
      right = set_dac (devc, 0x05, right);
      devc->gains[0] = left | (right << 8);
      mixer_devs[devc->mixer_dev]->modify_counter++;
      return 0;
      break;
    }
  return OSS_EINVAL;
}

static int
julia_set_control (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_EINVAL;
    
	return devc->monitor[ctrl];
    }

  if (cmd == SNDCTL_MIX_WRITE)
    {
    	if (ctrl < 0 || ctrl > 4)
	    return OSS_EINVAL;
	
	value = !!value;
	julia_Monitor (devc, value, ctrl);
	return devc->monitor[ctrl];
    }

  return OSS_EINVAL;
}

static int
julia_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:		/* PCM */
	  left = set_dac (devc, 0x04, left);
	  right = set_dac (devc, 0x05, right);
	  break;
	case 1:		/* LINEIN */
	  left = set_dac (devc, 0x06, left);
	  right = set_dac (devc, 0x07, right);
	  break;
	case 2:		/* SPDIFOUT */
	  left = set_dac (devc, 0x08, left);
	  right = set_dac (devc, 0x09, right);
	  break;
	case 3:		/* SPDIFIN */
	  left = set_dac (devc, 0x0B, left);
	  right = set_dac (devc, 0x0C, right);
	  break;

	default:
	  return OSS_EINVAL;
	}

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

  return OSS_EINVAL;
}

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

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

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

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

  if ((err = mixer_ext_create_control (dev, group,
				       1, julia_set_ak4358,
				       MIXT_STEREOSLIDER,
				       "ENVY24_LINEIN", 0x7f,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group,
				       2, julia_set_ak4358,
				       MIXT_STEREOSLIDER,
				       "ENVY24_SPDIFOUT", 0x7f,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

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

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

  if ((err = mixer_ext_create_control (dev, monitor,
				       1, julia_set_control,
				       MIXT_ONOFF,
				       "ENVY24_LINEIN", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, monitor,
				       2, julia_set_control,
				       MIXT_ONOFF,
				       "ENVY24_SPDIFOUT", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, monitor,
				       3, julia_set_control,
				       MIXT_ONOFF,
				       "ENVY24_SPDIFIN", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  
  if ((err = mixer_ext_create_control (dev, monitor,
				       4, julia_set_control,
		    		       MIXT_ENUM,
				       "ENVY24_DEEMPH", 2,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
   return err;
  mixer_ext_set_strings (dev, err, "OFF 50/15usec", 0);
  
  return 0;
}

static void
julia_card_init (envy24ht_devc * devc)
{
  ak4114_init (devc);
/*
 * AK4358 DAC initialization
 */
  i2c_write (devc, AK4358_ADDRESS, 2, 0x00);
  i2c_write (devc, AK4358_ADDRESS, 2, 0x4E);
  i2c_write (devc, AK4358_ADDRESS, 0, 0x06);
  i2c_write (devc, AK4358_ADDRESS, 1, 0x02);
  i2c_write (devc, AK4358_ADDRESS, 3, 0x01);

  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);

  i2c_write (devc, AK4358_ADDRESS, 0xA, 0x00);
  i2c_write (devc, AK4358_ADDRESS, 0xD, 0x00);
  i2c_write (devc, AK4358_ADDRESS, 0xE, 0x00);
  i2c_write (devc, AK4358_ADDRESS, 0xF, 0x00);
  i2c_write (devc, AK4358_ADDRESS, 2, 0x4F);

  julia_Monitor (devc, 0, 0); /* Unmute */
}

static void
julia_card_uninit (envy24ht_devc * devc)
{
    if (devc->timeout_id != 0)
       untimeout (devc->timeout_id);
}

envy24ht_auxdrv_t envy24ht_julia_auxdrv = {
  julia_card_init,
  julia_mixer_init,
  julia_set_rate,
  NULL,
  NULL,
  julia_set_syncsource,
  julia_audio_ioctl,
  julia_card_uninit
};
