/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for control of SoundBlaster mixer
 *  TODO: Routines for CTL1335 mixer (Sound Blaster 2.0 CD).
 *        If you have this soundcard, mail me!!!
 *
 *
 *   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 "pcm.h"
#include "mixer.h"
#include "sb.h"

void snd_sbmixer_write( sbmixer_t *mixer, unsigned char reg, unsigned char data )
{
  outb( reg, SBP1( mixer -> port, MIXER_ADDR ) );
  snd_delay( 1 );
  outb( data, SBP1( mixer -> port, MIXER_DATA ) );
  snd_delay( 1 );
}

unsigned char snd_sbmixer_read( sbmixer_t *mixer, unsigned char reg )
{
  unsigned char result;
  
  outb( reg, SBP1( mixer -> port, MIXER_ADDR ) );
  snd_delay( 1 );
  result = inb( SBP1( mixer -> port, MIXER_DATA ) );
  snd_delay( 1 );
  return result;
}

/*
 *    private_value
 *		0x000000ff - register
 *              0x00000f00 - left shift
 *              0x0000f000 - right shift
 */

static void snd_sbmixer_record_source( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int enable )
{  
  unsigned long flags;
  unsigned char mixs = SB_DSP_MIXS_NONE, mask;
  sbmixer_t *sbmix;
  
  sbmix = (sbmixer_t *)mixer -> private_data;
  switch ( channel -> hw.priority ) {
    case SND_MIXER_PRI_MIC:	mask = 1; break;
    case SND_MIXER_PRI_CD:	mask = 2; break;
    case SND_MIXER_PRI_LINE:	mask = 4; break;
    default: 			mask = 0; break;	/* master */
  }
  if ( enable )
    mixer -> private_value |= mask;
   else
    mixer -> private_value &= ~mask;

  if ( mixer -> private_value == 0 ) mixs = SB_DSP_MIXS_NONE; else
  if ( mixer -> private_value & 1 ) mixs = SB_DSP_MIXS_MIC; else
  if ( mixer -> private_value & 2 ) mixs = SB_DSP_MIXS_CD; else
  mixs = SB_DSP_MIXS_LINE;
        
  snd_spin_lock( sbmix, mixer, &flags );
  mixs |= snd_sbmixer_read( sbmix, SB_DSP_RECORD_SOURCE ) & ~6;
  snd_sbmixer_write( sbmix, SB_DSP_RECORD_SOURCE, mixs );
  snd_spin_unlock( sbmix, mixer, &flags );
}

static void snd_sbmixer_volume_level( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int left, int right )
{
  unsigned long flags;
  unsigned char reg, left_shift, right_shift, data, mask;
  sbmixer_t *sbmix;
  
  sbmix = (sbmixer_t *)mixer -> private_data;
  reg = (unsigned char)channel -> hw.private_value;
  left_shift = (unsigned char)((channel -> hw.private_value >> 8) & 0x0f);
  right_shift = (unsigned char)((channel -> hw.private_value >> 12) & 0x0f);
#if 0
  snd_printk( "volume: reg = 0x%x, left_shift = 0x%x, right_shift = 0x%x\n", reg, left_shift, right_shift );
#endif
  left <<= left_shift;
  mask = channel -> hw.max << left_shift;
  if ( left_shift != right_shift ) {
    left |= right << right_shift;
    mask |= channel -> hw.max << right_shift;
  }
  snd_spin_lock( sbmix, mixer, &flags );
  data = snd_sbmixer_read( sbmix, reg ) & ~mask;
  snd_sbmixer_write( sbmix, reg, left | data );
  snd_spin_unlock( sbmix, mixer, &flags );
}

#define SB_DSP_MIXS (sizeof(snd_sbmixer_pro_mixs)/sizeof(struct snd_stru_mixer_channel_hw))
#define SB_DSP_PRIVATE( reg, left_shift, right_shift ) (reg|(left_shift<<8)|(right_shift<<12))

static struct snd_stru_mixer_channel_hw snd_sbmixer_pro_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, 1, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 7,			/* max value */
    -4600, 0, 575,		/* min, max, step - dB */
    SB_DSP_PRIVATE( SB_DSP_MASTER_DEV, 5, 1 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sbmixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_PCM,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_PCM,		/* device name */
    SND_MIXER_OSS_PCM,		/* OSS device # */
    0, 1, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 7,			/* max value */
    -4600, 0, 575,		/* min, max, step - dB */
    SB_DSP_PRIVATE( SB_DSP_PCM_DEV, 5, 1 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sbmixer_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, 1, 1, 0,	1,		/* mute/stereo/record/digital/input */
    0, 7,			/* max value */
    -4600, 0, 575,		/* min, max, step - dB */
    SB_DSP_PRIVATE( SB_DSP_LINE_DEV, 5, 1 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_sbmixer_record_source,	/* record source */
    NULL,			/* set mute */
    snd_sbmixer_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, 1, 0,	1,		/* mute/stereo/record/digital/input */
    0, 7,			/* max value */
    -4600, 0, 575,		/* min, max, step - dB */
    SB_DSP_PRIVATE( SB_DSP_CD_DEV, 5, 1 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_sbmixer_record_source,	/* record source */
    NULL,			/* set mute */
    snd_sbmixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_FM,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_FM,		/* device name */
    SND_MIXER_OSS_SYNTH,	/* OSS device # */
    0, 1, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 7,			/* max value */
    -4600, 0, 575,		/* min, max, step - dB */
    SB_DSP_PRIVATE( SB_DSP_CD_DEV, 5, 1 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_sbmixer_record_source,	/* record source */
    NULL,			/* set mute */
    snd_sbmixer_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,	0,		/* mute/stereo/record/digital/input */
    0, 3,			/* max value */
    -4600, 0, 1150,		/* min, max, step - dB */
    SB_DSP_PRIVATE( SB_DSP_MIC_DEV, 1, 1 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_sbmixer_record_source,	/* record source */
    NULL,			/* set mute */
    snd_sbmixer_volume_level,	/* set volume level */
  }
};

/*
 *    private_value
 *		0x000000ff - register
 *              0x00000f00 - left shift
 *              0x0000f000 - right shift
 */

static void snd_sb16mixer_record_source( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int enable )
{  
  unsigned long flags;
  unsigned int mask;
  sbmixer_t *sbmix;
  
  sbmix = (sbmixer_t *)mixer -> private_data;  
  switch ( channel -> hw.priority ) {
    case SND_MIXER_PRI_MIC:	mask = 0x01; break;
    case SND_MIXER_PRI_CD:	mask = 0x04; break;
    case SND_MIXER_PRI_LINE:	mask = 0x10; break;
    case SND_MIXER_PRI_SYNTHESIZER: mask = 0x40; break;
    default: 			mask = 0; break;	/* master */
  }
  if ( enable )
    mixer -> private_value |= mask;
   else
    mixer -> private_value &= ~mask;

  snd_spin_lock( sbmix, mixer, &flags );
  snd_sbmixer_write( sbmix, SB_DSP4_INPUT_LEFT, ((unsigned char)mixer -> private_value) & 0xff );
  mask = ((unsigned char)mixer -> private_value >> 1) & 0xfe;
  mask |= (unsigned char)mixer -> private_value & 0x01;
  snd_sbmixer_write( sbmix, SB_DSP4_INPUT_RIGHT, mask );
  snd_spin_unlock( sbmix, mixer, &flags );
}

static void snd_sb16mixer_mute( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, unsigned int mute )
{
  unsigned long flags;
  unsigned int lmask, rmask;
  sbmixer_t *sbmix;
  
  sbmix = (sbmixer_t *)mixer -> private_data;  
  switch ( channel -> hw.priority ) {
    case SND_MIXER_PRI_MIC: lmask = rmask = 0x0100; break;
    case SND_MIXER_PRI_CD: lmask = 0x0400; rmask = 0x0200; break;
    case SND_MIXER_PRI_LINE: lmask = 0x1000; rmask = 0x0800; break;
    default:
      lmask = rmask = 0;
  }
  if ( mute & SND_MIX_MUTE_LEFT )
    mixer -> private_value &= ~lmask;
   else
    mixer -> private_value |= lmask;
  if ( mute & SND_MIX_MUTE_RIGHT )
    mixer -> private_value &= ~rmask;
   else
    mixer -> private_value |= rmask;
#if 0
  snd_printk( "private_value = 0x%x\n", mixer -> private_value );
#endif
  snd_spin_lock( sbmix, mixer, &flags );
  snd_sbmixer_write( sbmix, 0x3c, (mixer -> private_value >> 8) & 0xff );
  snd_spin_unlock( sbmix, mixer, &flags );
}

static void snd_sb16mixer_volume_level( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int left, int right )
{
  unsigned long flags;
  unsigned char reg, left_shift, right_shift, data, mask;
  sbmixer_t *sbmix;
  
  sbmix = (sbmixer_t *)mixer -> private_data;
  reg = (unsigned char)channel -> hw.private_value;
  left_shift = (unsigned char)((channel -> hw.private_value >> 8) & 0x0f);
  right_shift = (unsigned char)((channel -> hw.private_value >> 12) & 0x0f);
#if 0
  snd_printk( "volume: reg = 0x%x, left_shift = 0x%x, right_shift = 0x%x\n", reg, left_shift, right_shift );
#endif
  left <<= left_shift;
  mask = channel -> hw.max << left_shift;
  right <<= right_shift;
  snd_spin_lock( sbmix, mixer, &flags );
  data = snd_sbmixer_read( sbmix, reg ) & ~mask;
  snd_sbmixer_write( sbmix, reg, left | data );
  if ( channel -> hw.stereo ) {
    data = snd_sbmixer_read( sbmix, reg + 1 ) & ~mask;
    snd_sbmixer_write( sbmix, reg + 1, right | data );
  }
  snd_spin_unlock( sbmix, mixer, &flags );
}

#define SB_DSP4_MIXS (sizeof(snd_sbmixer_16_mixs)/sizeof(struct snd_stru_mixer_channel_hw))
#define SB_DSP4_PRIVATE( reg, left_shift, right_shift ) (reg|(left_shift<<8)|(right_shift<<12))

static struct snd_stru_mixer_channel_hw snd_sbmixer_16_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, 1, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 31,			/* min, max value */
    -6200, 0, 200,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_MASTER_DEV, 3, 3 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_BASS,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_BASS,		/* device name */
    SND_MIXER_OSS_BASS,		/* OSS device # */
    0, 1, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 15,			/* min, max value */
    -1400, 1400, 200,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_BASS_DEV, 4, 4 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_TREBLE,	/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_TREBLE,	/* device name */
    SND_MIXER_OSS_TREBLE,	/* OSS device # */
    0, 1, 0, 0, 0,		/* mute/stereo/record/digital/input */
    0, 15,			/* min, max value */
    -1400, 1400, 200,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_TREBLE_DEV, 4, 4 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sb16mixer_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, 1, 0, 0,		/* mute/stereo/record/digital/input */
    0, 31,			/* min, max value */
    -6200, 0, 200,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_SYNTH_DEV, 3, 3 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_sb16mixer_record_source,/* record source */
    NULL,			/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_PCM,		/* priority */
    SND_MIXER_PRI_MASTER,	/* parent priority */
    SND_MIXER_ID_PCM,		/* device name */
    SND_MIXER_OSS_PCM,		/* OSS device # */
    0, 1, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 31,			/* max value */
    -6200, 0, 200,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_PCM_DEV, 3, 3 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_SPEAKER,	/* priority */
    SND_MIXER_PRI_PARENT,	/* parent priority */
    SND_MIXER_ID_SPEAKER,	/* device name */
    SND_MIXER_OSS_SPEAKER,	/* OSS device # */
    0, 0, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 3,			/* max value */
    -1800, 0, 600,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_SPEAKER_DEV, 6, 6 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_LINE,		/* priority */
    SND_MIXER_PRI_PARENT,	/* 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, 31,			/* max value */
    -6200, 0, 200,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_LINE_DEV, 3, 3 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_sb16mixer_record_source,/* record source */
    snd_sb16mixer_mute,		/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_MIC,		/* priority */
    SND_MIXER_PRI_PARENT,	/* parent priority */
    SND_MIXER_ID_MIC,		/* device name */
    SND_MIXER_OSS_MIC,		/* OSS device # */
    1, 0, 1, 0,	1,		/* mute/stereo/record/digital/input */
    0, 31,			/* max value */
    -6200, 0, 200,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_MIC_DEV, 3, 3 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_sb16mixer_record_source,/* record source */
    snd_sb16mixer_mute,		/* set mute */
    snd_sb16mixer_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 # */
    1, 1, 1, 0, 1,		/* mute/stereo/record/digital/input */
    0, 31,			/* max value */
    -6200, 0, 200,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_CD_DEV, 3, 3 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    snd_sb16mixer_record_source,/* record source */
    snd_sb16mixer_mute,		/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_IGAIN,	/* priority */
    SND_MIXER_PRI_PARENT,	/* parent priority */
    SND_MIXER_ID_IGAIN,		/* device name */
    SND_MIXER_OSS_IGAIN,	/* OSS device # */
    0, 1, 0, 0, 1,		/* mute/stereo/record/digital/input */
    0, 3,			/* max value */
    -1800, 0, 600,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_IGAIN_DEV, 6, 6 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  },
  {
    SND_MIXER_PRI_OGAIN,	/* priority */
    SND_MIXER_PRI_PARENT,	/* parent priority */
    SND_MIXER_ID_OGAIN,		/* device name */
    SND_MIXER_OSS_OGAIN,	/* OSS device # */
    0, 1, 0, 0,	0,		/* mute/stereo/record/digital/input */
    0, 3,			/* max value */
    -1800, 0, 600,		/* min, max, step - dB */
    SB_DSP4_PRIVATE( SB_DSP4_OGAIN_DEV, 6, 6 ),
    NULL,			/* compute dB -> linear */
    NULL,			/* compute linear -> dB */
    NULL,			/* record source */
    NULL,			/* set mute */
    snd_sb16mixer_volume_level,	/* set volume level */
  }
};

snd_kmixer_t *snd_sbdsp_new_mixer( snd_card_t *card,
                                   sbmixer_t *sbmix,
                                   unsigned short hardware )
{
  int idx, count;
  snd_kmixer_t *mixer;
  snd_kmixer_channel_t *channel;
  struct snd_stru_mixer_channel_hw *phw;
  unsigned long flags;
  char *name;

  switch ( hardware ) {
    case SB_HW_PRO:
      count = SB_DSP_MIXS;
      phw = snd_sbmixer_pro_mixs;
      name = "CTL1345";
      break;
    case SB_HW_16:
      count = SB_DSP4_MIXS;
      phw = snd_sbmixer_16_mixs;
      name = "CTL1745";
      break;
    default:
      return NULL;
  }
  if ( !card ) return NULL;
  mixer = snd_mixer_new( card, name );
  if ( !mixer ) return NULL;
  strcpy( mixer -> name, mixer -> id );
  mixer -> private_data = sbmix;
  for ( idx = 0; idx < count; idx++ ) {
    channel = snd_mixer_new_channel( mixer, &phw[ idx ] );
    if ( !channel ) {
      snd_mixer_free( mixer );
      return NULL;
    }
  }
  mixer -> hw.caps = SND_MIXER_INFO_CAP_EXCL_RECORD;
  snd_spin_lock( sbmix, mixer, &flags );
  snd_sbmixer_write( sbmix, 0x00, 0x00 );	/* mixer reset */
  snd_spin_unlock( sbmix, mixer, &flags );
  return mixer;
}
