/*
 *  Copyright (c) 1994/95 by Jaroslav Kysela (Perex soft)
 *  Routines for control of ICS2101 and CS4321 (mixer part only) chips
 */

#include "gus_dev.h"

#define CURRENT_MIX( a ) ( ( current_mix[ a ].left & 0xff ) | ( ( current_mix[ a ].right & 0xff ) << 8 ) )

struct current_mix_stru current_mix[ MIX_LAST ];

int gus_mix_gf1_default = 74;	/* only for GUS MAX are this values accepted */
int gus_mix_cd_default = 0;
int gus_mix_line_default = 0;
int gus_mix_mic_default = 0;
int gus_mix_gain_default = 20;
int gus_mix_output_default = 74;
int gus_mix_gf1_master_default = 65;

unsigned char gus_mix_ctrl_reg;

short codec_mute_output = 1;	/* enable output only when chip isn't in calibrate mode */

static unsigned int mix_devs;
static unsigned int mix_stereo_devs;
static unsigned int mix_rec_devs;
static unsigned int rec_src;

static void codec_set_mic_gain( short left, short right );
static void codec_set_source( short left, short right );
static void codec_set_dev( unsigned char dev, int left, int right );
static void ics_set_dev( unsigned char dev, int left, int right );

void mixer_init()
{
  short i;

  if ( ics_flag || codec_flag )	
  			/* revision v3.7, daughter board and MAX have MIXER */
    {
      mix_devs = SOUND_MASK_MIC | SOUND_MASK_LINE |
    	         SOUND_MASK_SYNTH | SOUND_MASK_PCM |
    	         SOUND_MASK_CD | SOUND_MASK_VOLUME |
    	         SOUND_MASK_IMIX;
      mix_stereo_devs = mix_devs & ~SOUND_MASK_IMIX;
      mix_rec_devs = SOUND_MASK_MIC | SOUND_MASK_LINE;
      if ( codec_flag ) 
        {
          mix_devs &= ~SOUND_MASK_VOLUME;
          mix_devs |= SOUND_MASK_RECLEV;
          mix_stereo_devs &= ~SOUND_MASK_MIC;
          if ( use_codec )
            mix_rec_devs |= SOUND_MASK_SYNTH;
        }
    }
   else
    {
      mix_devs = SOUND_MASK_MIC | SOUND_MASK_LINE | 
    	         SOUND_MASK_SYNTH | SOUND_MASK_PCM;
      mix_stereo_devs = SOUND_MASK_PCM;
      mix_rec_devs = SOUND_MASK_MIC | SOUND_MASK_LINE;
    }
  gus_mix_ctrl_reg &= ~0xf0;
  gus_mix_ctrl_reg |= 0x09;     /* enable latches, disable MIC IN & LINE IN */
                                /* enable LINE OUT */
  if ( ics_flag || codec_flag )
    {
      gus_mix_ctrl_reg &= ~1;	/* enable LINE IN - mixer overwrite this */
      gus_mix_ctrl_reg |= 4;	/* enable MIC IN - mixer overwrite this */
    }
  OUTB( gus_mix_ctrl_reg, GUSP( MIXCNTRLREG ) );
  OUTB( 0, GUSP( GF1PAGE ) );
  for ( i = 0; i < MIX_LAST; i++ )
    {
      current_mix[ i ].left_mute = 0;
      current_mix[ i ].left = 0;
      current_mix[ i ].right_mute = 0;
      current_mix[ i ].right = 0;
    }
  current_mix[ MIX_PCM ].left =
  current_mix[ MIX_PCM ].right = 
  current_mix[ MIX_GF1_MASTER ].left =
  current_mix[ MIX_GF1_MASTER ].right = gus_mix_gf1_master_default;
  if ( codec_flag )		/* CODEC */
    {
      codec_set_mute( CODEC_GF1_INPUT, 1, 1 );
      codec_set_dev( CODEC_GF1_INPUT, gus_mix_gf1_default, gus_mix_gf1_default );
      codec_set_mute( CODEC_CD_INPUT, 1, 1 );
      codec_set_dev( CODEC_CD_INPUT, gus_mix_cd_default, gus_mix_cd_default );
      codec_set_mute( CODEC_LINE_INPUT, 1, 1 );
      codec_set_dev( CODEC_LINE_INPUT, gus_mix_line_default, gus_mix_line_default );
      codec_set_mute( CODEC_MIC_INPUT, 1, 1 );
      codec_set_dev( CODEC_MIC_INPUT, gus_mix_mic_default, gus_mix_mic_default );	/* MIC is mono only */
      codec_set_mute( CODEC_INPUT, 1, 1 );
      codec_set_dev( CODEC_INPUT, gus_mix_gain_default, gus_mix_gain_default );
      codec_set_mute( CODEC_OUTPUT, 1, 1 );
      codec_set_dev( CODEC_OUTPUT, gus_mix_output_default, gus_mix_output_default );
      codec_set_mic_gain( CODEC_ENABLE_MIC_GAIN, CODEC_ENABLE_MIC_GAIN );
      codec_set_source( CODEC_MIXS_LINE, CODEC_MIXS_LINE );
    }
   else
  if ( ics_flag )			/* ICS mixer */
    {
      ics_set_dev( ICS_MIC_DEV, gus_mix_mic_default, gus_mix_mic_default );
      ics_set_dev( ICS_LINE_DEV, gus_mix_line_default, gus_mix_line_default );
      ics_set_dev( ICS_CD_DEV, gus_mix_cd_default, gus_mix_cd_default );
      ics_set_dev( ICS_GF1_DEV, gus_mix_gf1_default, gus_mix_gf1_default );
      ics_set_dev( ICS_NONE_DEV, 0, 0 );
      ics_set_dev( ICS_MASTER_DEV, gus_mix_output_default, gus_mix_output_default );
    }
  rec_src = 0;
}

static void codec_set_mic_gain( short left, short right )
{
  CLI();
  codec_outm( CODEC_LEFT_INPUT, ~0x20, left & 0x20 );
  codec_outm( CODEC_RIGHT_INPUT, ~0x20, right & 0x20 );
  STI();
}

static void codec_set_source( short left, short right )
{
  CLI();
  codec_outm( CODEC_LEFT_INPUT, (unsigned char)~0xc0, (unsigned char)left & 0xc0 );
  codec_outm( CODEC_RIGHT_INPUT, (unsigned char)~0xc0, (unsigned char)right & 0xc0 );
  STI();
}

void codec_set_mute( unsigned char dev, short left, short right )
{
  short tmp;

  switch ( dev ) {
    case CODEC_MIC_INPUT:	tmp = MIX_MIC;		break;
    case CODEC_LINE_INPUT:	tmp = MIX_LINE;		break;
    case CODEC_CD_INPUT:	tmp = MIX_CD;		break;
    case CODEC_GF1_INPUT:	tmp = MIX_GF1;		break;
    case CODEC_INPUT:		tmp = MIX_GAIN;		break;
    default:
      dev = CODEC_OUTPUT;
      tmp = MIX_PCM;
  }

  left &= 1;
  right &= 1;
  current_mix[ tmp ].left_mute = left;
  current_mix[ tmp ].right_mute = right;
  if ( codec_mute_output ) left = right = 0;	/* !!! mute !!! */
  
  if ( dev == CODEC_INPUT ) return;	/* I can't mute gain !!! */

  left = ( left << 7 ) ^ 0x80;
  right = ( right << 7 ) ^ 0x80;

  CLI();
  codec_outm( dev + 0, 0x7f, left );
  if ( dev != CODEC_MIC_INPUT )
    codec_outm( dev + 1, 0x7f, right );
  STI();
}

static void codec_set_dev( unsigned char dev, int left, int right )
{
  short tmp;

  if ( left < 0 ) left = 0;
  if ( right < 0 ) right = 0;
  if ( left > 100 ) left = 100;
  if ( right > 100 ) right = 100;

  switch ( dev ) {
    case CODEC_MIC_INPUT:	tmp = MIX_MIC;		break;
    case CODEC_LINE_INPUT:	tmp = MIX_LINE;		break;
    case CODEC_CD_INPUT:	tmp = MIX_CD;		break;
    case CODEC_GF1_INPUT:	tmp = MIX_GF1;		break;
    case CODEC_INPUT:		tmp = MIX_GAIN;		break;
    default:			
      dev = CODEC_OUTPUT; 
      tmp = MIX_PCM;
  }
  current_mix[ tmp ].left = left;
  current_mix[ tmp ].right = right;

  left = ( ( 100 - left ) * 999 ) / 3125;
  right = ( ( 100 - right ) * 999 ) / 3125;

#if 0
  PRINTK( "CODEC: dev=0x%x, left=%d, right=%d\n", dev, left, right );
#endif 

  CLI();

  if ( current_mix[ tmp ].left_mute && dev != CODEC_INPUT )
    if ( left == 31 )
      codec_outm( dev + 0, (unsigned char)~0x80, 0x80 );
     else
      codec_outm( dev + 0, (unsigned char)~0x80, 0x00 );
  if ( current_mix[ tmp ].right_mute && 
       dev != CODEC_MIC_INPUT && dev != CODEC_INPUT )
    if ( right == 31 )
      codec_outm( dev + 1, (unsigned char)~0x80, 0x80 );
     else
      codec_outm( dev + 1, (unsigned char)~0x80, 0x00 );

  switch ( dev ) {
    case CODEC_MIC_INPUT:
      codec_outm( CODEC_MIC_INPUT, ~0x0f, left >> 1 );
      break;
    case CODEC_INPUT:
      codec_outm( dev + 0, ~0x0f, 15 - ( left >> 1 ) );
      codec_outm( dev + 1, ~0x0f, 15 - ( right >> 1 ) );
      break;
    default:
      codec_outm( dev + 0, ~0x1f, left );
      codec_outm( dev + 1, ~0x1f, right );
  }
    
  STI();
}

static void ics_set_dev( unsigned char dev, int left, int right )
{
  short tmp;
  short addr = dev << 3;

  if ( left < 0 ) left = 0;
  if ( right < 0 ) right = 0;
  if ( left > 100 ) left = 100;
  if ( right > 100 ) right = 100;
  
  switch ( dev ) {
    case ICS_MIC_DEV:    tmp = MIX_MIC; 	break;
    case ICS_LINE_DEV:   tmp = MIX_LINE; 	break;
    case ICS_CD_DEV:     tmp = MIX_CD; 		break;
    case ICS_GF1_DEV:    tmp = MIX_GF1; 	break;
    default:
      dev = ICS_MASTER_DEV;
      tmp = MIX_MASTER;
  }
  current_mix[ tmp ].left = left;
  current_mix[ tmp ].right = right;
  
  left = ( ( 127 * left ) + 50 ) / 100;
  right = ( ( 127 * right ) + 50 ) / 100;

  if ( ics_flag == 5 && 		/* flip some channels */
       ( dev == ICS_GF1_DEV || dev == ICS_MASTER_DEV ) )
    {
      tmp = left; left = right; right = tmp;
    }
  
  CLI();
  OUTB( addr | 0, GUSP( MIXCNTRLPORT ) );
  OUTB( 1, GUSP( MIXDATAPORT ) );
  OUTB( addr | 2, GUSP( MIXCNTRLPORT ) );
  OUTB( (unsigned char)left, GUSP( MIXDATAPORT ) );
  OUTB( addr | 1, GUSP( MIXCNTRLPORT ) );
  OUTB( 2, GUSP( MIXDATAPORT ) );
  OUTB( addr | 3, GUSP( MIXCNTRLPORT ) );
  OUTB( (unsigned char)right, GUSP( MIXDATAPORT ) );
  STI();
}

static int mixer_set_recsrc( int src )
{
  if ( src >= 0 )
    {
      if ( !ics_flag && !codec_flag ) 
        {
          src &= SOUND_MASK_MIC | SOUND_MASK_LINE;
          gus_mix_ctrl_reg &= ~0x07;
          gus_mix_ctrl_reg |= 1;		/* disable MIC & LINE IN */
          if ( src & SOUND_MASK_MIC )
            gus_mix_ctrl_reg |= 4;		/* enable MIC IN */
          if ( src & SOUND_MASK_LINE )
            gus_mix_ctrl_reg &= ~1;		/* enable LINE IN */
          OUTB( gus_mix_ctrl_reg, GUSP( MIXCNTRLREG ) );
          OUTB( 0, GUSP( GF1PAGE ) );
          rec_src = src;
        }
       else
        {
          rec_src = src;
#if 0
          current_mix[ MIX_MIC ].left_mute = 
          current_mix[ MIX_MIC ].right_mute = unmute_mic;
          current_mix[ MIX_LINE ].left_mute = 
          current_mix[ MIX_LINE ].right_mute = unmute_line;
#endif
          if ( use_codec )
            {
              src = rec_src & SOUND_MASK_SYNTH ? CODEC_MIXS_GF1 : CODEC_MIXS_LINE;
              if ( rec_src & SOUND_MASK_MIC ) src |= CODEC_MIXS_MIC;
              codec_set_source( src, src );
            }
        }
    }
  return rec_src;
}

static int mixer_set_mic( int value )
{
  short left = value & 0xff;
  short right = ( value >> 8 ) & 0xff;
  
  if ( value >= 0 )
    { 
      if ( codec_flag )			/* CODEC mixer */
        codec_set_dev( CODEC_MIC_INPUT, ( left + right ) >> 1, 0 );
       else 
      if ( ics_flag )			/* ICS mixer */
        ics_set_dev( ICS_MIC_DEV, left, right );
       else
        {
          if ( left > 10 && ( rec_src & SOUND_MASK_MIC ) == 0 ) 
            mixer_set_recsrc( rec_src | SOUND_MASK_MIC );
          if ( left <= 10 && ( rec_src & SOUND_MASK_MIC ) != 0 )
            mixer_set_recsrc( rec_src & ~SOUND_MASK_MIC );
        }
    }
  return CURRENT_MIX( MIX_MIC );
}

static int mixer_set_cd( int value )
{
  short left = value & 0xff;
  short right = ( value >> 8 ) & 0xff;
  
  if ( value >= 0 )
    { 
      if ( codec_flag )			/* CODEC mixer */
        codec_set_dev( CODEC_CD_INPUT, left, right );
       else 
      if ( ics_flag )			/* ICS mixer */
        ics_set_dev( ICS_CD_DEV, left, right );
       else
        return -EINVAL;
    }
  return CURRENT_MIX( MIX_CD );
}

static int mixer_set_line( int value )
{
  short left = value & 0xff;
  short right = ( value >> 8 ) & 0xff;
  
  if ( value >= 0 )
    { 
      if ( codec_flag )			/* CODEC mixer */
        codec_set_dev( CODEC_LINE_INPUT, left, right );
       else 
      if ( ics_flag )			/* ICS mixer */
        ics_set_dev( ICS_LINE_DEV, left, right );
       else
        {
          if ( left > 10 && ( rec_src & SOUND_MASK_LINE ) == 0 ) 
            mixer_set_recsrc( rec_src | SOUND_MASK_LINE );
          if ( left < 10 && ( rec_src & SOUND_MASK_LINE ) != 0 )
            mixer_set_recsrc( rec_src & ~SOUND_MASK_MIC );
        }
    }
  return CURRENT_MIX( MIX_LINE );
}

static int mixer_set_gf1_master( int value )
{
  short left = value & 0xff;
  short right = ( value >> 8 ) & 0xff;
  
  if ( value >= 0 )
    { 
      if ( left < 0 ) left = 0;
      if ( left > 100 ) left = 100;
      if ( right < 0 ) right = 0;
      if ( right > 100 ) right = 100;
      current_mix[ MIX_GF1_MASTER ].left = left;
      current_mix[ MIX_GF1_MASTER ].right = right;
      recompute_gf1_ramp_ranges();
    }
  return CURRENT_MIX( MIX_GF1_MASTER );
}

static int mixer_set_gf1( int value )
{
  short left = value & 0xff;
  short right = ( value >> 8 ) & 0xff;
  
  if ( value >= 0 )
    { 
      if ( codec_flag )			/* CODEC mixer */
        codec_set_dev( CODEC_GF1_INPUT, left, right );
       else 
      if ( ics_flag )			/* ICS mixer */
        ics_set_dev( ICS_GF1_DEV, left, right );
       else
        return mixer_set_gf1_master( value );
    }
  return CURRENT_MIX( MIX_GF1 );
}

static int mixer_set_pcm( int value )
{
  short left = value & 0xff;
  short right = ( value >> 8 ) & 0xff;
  
  if ( value >= 0 )
    {
      if ( codec_flag )
        codec_set_dev( CODEC_OUTPUT, left, right );
       else
        { 
          current_mix[ MIX_PCM ].left = left;
          current_mix[ MIX_PCM ].right = right;
        }
      if ( gf1_mode == GF1_MODE_PCM )
        gf1_pcm_new_volume();
    }
  return CURRENT_MIX( MIX_PCM );
}

static int mixer_set_gain( int value )
{
  short left = value & 0xff;
  short right = ( value >> 8 ) & 0xff;
  
  if ( value >= 0 )
    {
      if ( codec_flag )
        codec_set_dev( CODEC_INPUT, left, right );
       else
        return -EINVAL;
    }
  return CURRENT_MIX( MIX_GAIN );
}

static int mixer_set_master( int value )
{
  short left = value & 0xff;
  short right = ( value >> 8 ) & 0xff;
  
  if ( value >= 0 )
    { 
      if ( ics_flag )			/* ICS mixer */
        ics_set_dev( ICS_MASTER_DEV, left, right );
       else
        return -EINVAL;
    }
  return CURRENT_MIX( MIX_MASTER );
}

int gus_ioctl_mixer( unsigned int cmd, unsigned long arg )
{
  if ( ( ( cmd >> 8 ) & 0xff ) == 'M' )
    {
      if ( cmd & IOC_IN )
        switch ( cmd & 0xff ) {
          case SOUND_MIXER_RECSRC:
            return IOCTL_OUT( arg, mixer_set_recsrc( IOCTL_IN( arg ) & mix_devs ) );
          case SOUND_MIXER_MIC:
            return IOCTL_OUT( arg, mixer_set_mic( IOCTL_IN( arg ) ) );  
          case SOUND_MIXER_CD:
            return IOCTL_OUT( arg, mixer_set_cd( IOCTL_IN( arg ) ) );
          case SOUND_MIXER_LINE:
            return IOCTL_OUT( arg, mixer_set_line( IOCTL_IN( arg ) ) );
          case SOUND_MIXER_SYNTH:
            return IOCTL_OUT( arg, mixer_set_gf1( IOCTL_IN( arg ) ) );
          case SOUND_MIXER_PCM:
            return IOCTL_OUT( arg, mixer_set_pcm( IOCTL_IN( arg ) ) );
          case SOUND_MIXER_RECLEV:
            return IOCTL_OUT( arg, mixer_set_gain( IOCTL_IN( arg ) ) );
          case SOUND_MIXER_VOLUME:
            return IOCTL_OUT( arg, mixer_set_master( IOCTL_IN( arg ) ) );  
          case SOUND_MIXER_IMIX:
            return IOCTL_OUT( arg, mixer_set_gf1_master( IOCTL_IN( arg ) ) );
        }
       else
      if ( cmd & IOC_OUT )
        switch ( cmd & 0xff ) {
          case SOUND_MIXER_DEVMASK:
            return IOCTL_OUT( arg, mix_devs );
          case SOUND_MIXER_STEREODEVS:
            return IOCTL_OUT( arg, mix_stereo_devs ); 
          case SOUND_MIXER_RECMASK:
            return IOCTL_OUT( arg, mix_rec_devs );
	  case SOUND_MIXER_CAPS:
	    return IOCTL_OUT( arg, 0 );
          case SOUND_MIXER_RECSRC:
            return IOCTL_OUT( arg, mixer_set_recsrc( -1 ) );
          case SOUND_MIXER_MIC:
            return IOCTL_OUT( arg, mixer_set_mic( -1 ) );
          case SOUND_MIXER_CD:
            return IOCTL_OUT( arg, mixer_set_cd( -1 ) );
          case SOUND_MIXER_LINE:
            return IOCTL_OUT( arg, mixer_set_line( -1 ) );
          case SOUND_MIXER_SYNTH:
            return IOCTL_OUT( arg, mixer_set_gf1( -1 ) );
          case SOUND_MIXER_PCM:
            return IOCTL_OUT( arg, mixer_set_pcm( -1 ) );
          case SOUND_MIXER_RECLEV:
            return IOCTL_OUT( arg, mixer_set_gain( -1 ) );
          case SOUND_MIXER_VOLUME:
            return IOCTL_OUT( arg, mixer_set_master( -1 ) );
          case SOUND_MIXER_IMIX:
            return IOCTL_OUT( arg, mixer_set_gf1_master( -1 ) );
        }
    }
#if 0
  PRINTK( "MIXER ERR\n" );
  PRINTK( "  MIXER REQUEST: 0x%x, 0x%x\n", cmd, (int)IOCTL_IN( arg ) );
#endif
  return -EINVAL;
}

int gus_open_mixer()
{
  MOD_INC_USE_COUNT;
  return 0;
}

void gus_release_mixer()
{
  MOD_DEC_USE_COUNT;
}
