/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for control of ICS 2101 chip and "mixer" in GF1 chip
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define __SND_OSS_COMPAT__
#include "driver.h"
#include "mixer.h"
#include "gus.h"

/*
 *
 */

static void snd_gf1_mixer_update( snd_gus_card_t *gus, unsigned int private_value )
{
  unsigned long flags;
  unsigned int mixs;

  snd_spin_lock( gus, reg, &flags );
  /* disable MIC & LINE IN & LINE OUT */
  mixs = (gus -> mix_cntrl_reg & ~0x07) | 3;
  if ( private_value ) {
    if ( private_value & 0x101 ) mixs |= 4;		/* MIC */
    if ( private_value & 0x202 ) mixs &= ~1;		/* LINE IN */
    if ( private_value & 0x808 ) mixs &= ~2;		/* LINE OUT */
  }        
  outb( gus -> mix_cntrl_reg = mixs, GUSP( gus, MIXCNTRLREG ) );
  outb( gus -> gf1.active_voice = 0, GUSP( gus, GF1PAGE ) );
  snd_spin_unlock( gus, reg, &flags );
}

/*
 *    private_value
 *		not used
 */

static void snd_gf1_mixer_record_source( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int enable )
{  
  unsigned int mask;
  snd_gus_card_t *gus;
  
  gus = (snd_gus_card_t *)mixer -> private_data;
  switch ( channel -> hw.priority ) {
    case SND_MIXER_PRI_MIC:	mask = 1; break;
    case SND_MIXER_PRI_LINE:	mask = 2; break;
    default: 			mask = 0; break;	/* no change */
  }
  if ( enable )
    mixer -> private_value |= mask;
   else
    mixer -> private_value &= ~mask;
#if 0
  snd_printk( "recsrc - private_value = 0x%x\n", mixer -> private_value );
#endif
  snd_gf1_mixer_update( gus, mixer -> private_value );
}

static void snd_gf1_mixer_volume_level( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int left, int right )
{
  unsigned int mask;
  snd_gus_card_t *gus;
  
  gus = (snd_gus_card_t *)mixer -> private_data;
  switch ( channel -> hw.priority ) {
    case SND_MIXER_PRI_MIC:	mask = 0x100; break;
    case SND_MIXER_PRI_LINE:	mask = 0x200; break;
    case SND_MIXER_PRI_MASTER:  mask = 0x800; break;
    default:
      return;
  }
  if ( left > 0 )
    mixer -> private_value |= mask;
   else
    mixer -> private_value &= ~mask;
  snd_gf1_mixer_update( gus, mixer -> private_value );
}

#define GUS_GF1_MIXS (sizeof(snd_gf1_mixs)/sizeof(struct snd_stru_mixer_channel_hw))

static struct snd_stru_mixer_channel_hw snd_gf1_mixs[] = {
  {
    SND_MIXER_PRI_MASTER,	/* priority */
    SND_MIXER_PRI_PARENT,	/* parent priority */
    SND_MIXER_ID_MASTER,	/* device name */
    SND_MIXER_OSS_VOLUME,	/* OSS device # */
    0, 0, 0, 0, 0,		/* mute/stereo/record/digital/input */
    0, 1,			/* min, max value */
    -9600, 0, 9600,		/* min, max, step - dB */
    0,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_gf1_mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_LINE,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_LINE,		/* device name */
    SND_MIXER_OSS_LINE,		/* OSS device # */
    0, 0, 1, 0, 1,		/* mute/stereo/record/digital/input */
    0, 1,			/* min, max value */
    -9600, 0, 9600,		/* min, max, step - dB */
    0,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_gf1_mixer_record_source,/* record source */
    NULL,			/* set mute */
    snd_gf1_mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_MIC,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_MIC,		/* device name */
    SND_MIXER_OSS_MIC,		/* OSS device # */
    0, 0, 1, 0,	1,		/* mute/stereo/record/digital/input */
    0, 1,			/* min, max value */
    -9600, 0, 9600,		/* min, max, step - dB */
    0,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_gf1_mixer_record_source,/* record source */
    NULL,			/* set mute */
    snd_gf1_mixer_volume_level,	/* set volume level */
  }
};

#define GUS_GF1_MIXS_ACE (sizeof(snd_gf1_mixs_ace)/sizeof(struct snd_stru_mixer_channel_hw))

static struct snd_stru_mixer_channel_hw snd_gf1_mixs_ace[] = {
  {
    SND_MIXER_PRI_MASTER,	/* priority */
    SND_MIXER_PRI_PARENT,	/* parent priority */
    SND_MIXER_ID_MASTER,	/* device name */
    SND_MIXER_OSS_VOLUME,	/* OSS device # */
    0, 0, 0, 0, 0,		/* mute/stereo/record/digital/input */
    0, 1,			/* min, max value */
    -9600, 0, 9600,		/* min, max, step - dB */
    0,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_gf1_mixer_volume_level,	/* set volume level */
  }
};

/*
 *    private_value
 *		0x000000ff - register (device)
 */

static void snd_gf1_ics_mute( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, unsigned int mute )
{
  unsigned int mask;
  snd_gus_card_t *gus;
  
  gus = (snd_gus_card_t *)mixer -> private_data;
  switch ( channel -> hw.priority ) {
    case SND_MIXER_PRI_MIC:	mask = 0x100; break;
    case SND_MIXER_PRI_LINE:	mask = 0x200; break;
    case SND_MIXER_PRI_MASTER:  mask = 0x800; break;
    default:
      return;
  }
  if ( mute & SND_MIX_MUTE ) {
    mixer -> private_value &= ~mask;
  } else {
    mixer -> private_value |= mask;
  }
#if 0
  snd_printk( "mute - private_value = 0x%x\n", mixer -> private_value );
#endif
  snd_gf1_mixer_update( gus, mixer -> private_value );
}

static void snd_gf1_ics_volume_level( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int left, int right )
{
  unsigned long flags;
  unsigned int tmp, addr;
  snd_gus_card_t *gus;
  
  gus = (snd_gus_card_t *)mixer -> private_data;  
  addr = (unsigned char)channel -> hw.private_value;
  if ( gus -> ics_flag && gus -> ics_flipped &&
       (channel -> hw.priority == SND_MIXER_PRI_SYNTHESIZER ||
        channel -> hw.priority == SND_MIXER_PRI_MASTER) ) {
    tmp = left; left = right; right = tmp;
  }
#if 0
  printk( "mixer setup - addr = %i, left = %i, right = %i\n", addr, left, right );
#endif
  addr <<= 3;
  snd_spin_lock( gus, reg, &flags );
  outb( addr | 0, GUSP( gus, MIXCNTRLPORT ) );
  outb( 1, GUSP( gus, MIXDATAPORT ) );
  outb( addr | 2, GUSP( gus, MIXCNTRLPORT ) );
  outb( (unsigned char)left, GUSP( gus, MIXDATAPORT ) );
  outb( addr | 1, GUSP( gus, MIXCNTRLPORT ) );
  outb( 2, GUSP( gus, MIXDATAPORT ) );
  outb( addr | 3, GUSP( gus, MIXCNTRLPORT ) );
  outb( (unsigned char)right, GUSP( gus, MIXDATAPORT ) );                
  snd_spin_unlock( gus, reg, &flags );
}

#define GUS_ICS_MIXS (sizeof(snd_gf1_ics_mixs)/sizeof(struct snd_stru_mixer_channel_hw))

static struct snd_stru_mixer_channel_hw snd_gf1_ics_mixs[] = {
  {
    SND_MIXER_PRI_MASTER,	/* priority */
    SND_MIXER_PRI_PARENT,	/* parent priority */
    SND_MIXER_ID_MASTER,	/* device name */
    SND_MIXER_OSS_VOLUME,	/* OSS device # */
    1, 1, 0, 0, 0,		/* mute/stereo/record/digital/input */
    0, 127,			/* min, max value */
    -9600, 0, 75,		/* min, max, step - dB */
    SND_ICS_MASTER_DEV,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    snd_gf1_ics_mute,		/* set mute */
    snd_gf1_ics_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_SYNTHESIZER,	/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_SYNTHESIZER,	/* device name */
    SND_MIXER_OSS_SYNTH,	/* OSS device # */
    0, 1, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 127,			/* min, max value */
    -9600, 0, 75,		/* min, max, step - dB */
    SND_ICS_GF1_DEV,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_gf1_ics_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_LINE,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_LINE,		/* device name */
    SND_MIXER_OSS_LINE,		/* OSS device # */
    1, 1, 1, 0,	1,		/* mute/stereo/record/digital/input */
    0, 127,			/* min, max value */
    -9600, 0, 75,		/* min, max, step - dB */
    SND_ICS_LINE_DEV,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_gf1_mixer_record_source, /* record source */
    snd_gf1_ics_mute,		/* set mute */
    snd_gf1_ics_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_MIC,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_MIC,		/* device name */
    SND_MIXER_OSS_MIC,		/* OSS device # */
    1, 1, 1, 0,	1,		/* mute/stereo/record/digital/input */
    0, 127,			/* min, max value */
    -9600, 0, 75,		/* min, max, step - dB */
    SND_ICS_MIC_DEV,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_gf1_mixer_record_source, /* record source */
    snd_gf1_ics_mute,		/* set mute */
    snd_gf1_ics_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_CD,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_CD,		/* device name */
    SND_MIXER_OSS_CD,		/* OSS device # */
    0, 1, 0, 0,	1,		/* mute/stereo/record/digital/input */
    0, 127,			/* min, max value */
    -9600, 0, 75,		/* min, max, step - dB */
    SND_ICS_CD_DEV,
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_gf1_ics_volume_level,	/* set volume level */
  }
};

snd_kmixer_t *snd_gf1_new_mixer( snd_gus_card_t *gus )
{
  int idx, count;
  snd_kmixer_t *mixer;
  snd_kmixer_channel_t *channel;
  struct snd_stru_mixer_channel_hw *phw;

  if ( gus -> ics_flag ) {
    phw = snd_gf1_ics_mixs;
    count = GUS_ICS_MIXS;
  } else if ( gus -> card -> type == SND_CARD_TYPE_GUS_ACE ) {
    phw = snd_gf1_mixs_ace;
    count = GUS_GF1_MIXS_ACE;
  } else {
    phw = snd_gf1_mixs;
    count = GUS_GF1_MIXS;
  }
  if ( !gus ) return NULL;
  mixer = snd_mixer_new( gus -> card, gus -> ics_flag ? "ICS2101" : "GF1" );
  if ( !mixer ) return NULL;
  strcpy( mixer -> name, mixer -> id );
  for ( idx = 0; idx < count; idx++ ) {
    channel = snd_mixer_new_channel( mixer, &phw[ idx ] );
    if ( !channel ) {
      snd_mixer_free( mixer );
      return NULL;
    }
  }
  mixer -> private_data = gus;
  return mixer;
}
