#define _PAS2_MIXER_C_

/* linux/kernel/chr_drv/sound/pas2_mixer.c

Mixer routines for the Pro Audio Spectrum cards.

(C) 1992  Hannu Savolainen (hsavolai@cs.helsinki.fi) 
	  Craig Metz (cmetz@thor.tjhsst.edu) */

#include "sound_config.h"

#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_PAS)

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/fcntl.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/tty.h>
#include <linux/ctype.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <sys/kd.h>
#include <linux/wait.h>
#include <linux/soundcard.h>
#include "sound_calls.h"
#include "dev_table.h"

#include "pas.h"

#define TRACE(what)	/* (what) */

#define DISABLE_INTR(flags)	__asm__ __volatile__("pushfl ; popl %0 ; cli":"=r" (flags));
#define ENABLE_INTR(flags)	__asm__ __volatile__("pushfl ; popl %0 ; sti":"=r" (flags));
#define RESTORE_INTR(flags)	__asm__ __volatile__("pushl %0 ; popfl": :"r" (flags));

extern int translat_code;

static int rec_devices = (SOUND_MASK_MIC);	/* Default recording source */

#define POSSIBLE_RECORDING_DEVICES	(SOUND_MASK_SYNTH | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
					 SOUND_MASK_CD | SOUND_MASK_ALTPCM)

#define SUPPORTED_MIXER_DEVICES		(SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \
					 SOUND_MASK_CD /*|SOUND_MASK_ALTPCM*/ | SOUND_MASK_IMIX | \
					 SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_RECLEV)

static unsigned short levels[SOUND_MIXER_NRDEVICES] = { 	
				0x3232, /* Master Volume */
				0x3232, /* Bass */
				0x3232, /* Trebble */
				0x5050, /* FM */
				0x4b4b, /* PCM */
				0x3232, /* PC Speaker */
			 	0x4b4b, /* Ext Line */
			 	0x4b4b, /* Mic */
			 	0x4b4b, /* CD */
				0x6464, /* Recording monitor */
			 	0x0000, /* SB PCM */
			 	0x6464};/* Recording level */

static int mixer_output(int right_vol, int left_vol, int div, int bits, 
			int mixer /* Input or output mixer */)
{
	int left = left_vol * div / 100;
	int right = right_vol * div / 100;

	/* 
		The Revision D cards have a problem with their MVA508 interface.
		The kludge-o-rama fix is to make a 16-bit quantity with identical
		LSB and MSBs out of the output byte and to do a 16-bit out to the
		mixer port - 1.
		We don't need to do this because the call to pas_write more than
		compensates for the timing problems.
	*/

	if (bits & P_M_MV508_MIXER)
	{	/* Select input or output mixer */
		left  |= mixer;
		right |= mixer;
	}

	if (bits == P_M_MV508_BASS || bits == P_M_MV508_TREBLE)
	{	/* Bass and trebble are mono devices	*/
		pas_write(P_M_MV508_ADDRESS | bits, PARALLEL_MIXER);
		pas_write(left, PARALLEL_MIXER);
		right_vol = left_vol;
	} else {
		pas_write(P_M_MV508_ADDRESS | P_M_MV508_LEFT | bits, PARALLEL_MIXER);
		pas_write(left, PARALLEL_MIXER);
		pas_write(P_M_MV508_ADDRESS | P_M_MV508_RIGHT | bits, PARALLEL_MIXER);
		pas_write(right, PARALLEL_MIXER);
	}

	return (left_vol | (right_vol << 8));
}

static int pas_mixer_set(int whichDev, unsigned int level)
{
	int left, right, devmask, changed, i, mixer = 0;

	TRACE(printk("static int pas_mixer_set(int whichDev = %d, unsigned int level = %X)\n", whichDev, level));

	left = level & 0x7f;
	right = (level & 0x7f00) >> 8;

	if (whichDev < SOUND_MIXER_NRDEVICES)
	   if ((1 << whichDev) & rec_devices)
	      mixer = P_M_MV508_INPUTMIX; else mixer = P_M_MV508_OUTPUTMIX;

	switch(whichDev) {
		case SOUND_MIXER_VOLUME:	/* Master volume (0-63) */
			levels[whichDev] = mixer_output(right, left, 63, P_M_MV508_MASTER_A, 0);
			break;

	/*	Note! Bass and Treble are mono devices. Will use just the left channel.		*/
		case SOUND_MIXER_BASS:	/* Bass (0-12) */
			levels[whichDev] = mixer_output(right, left, 12, P_M_MV508_BASS, 0);
			break;
		case SOUND_MIXER_TREBLE:	/* Treble (0-12) */
			levels[whichDev] = mixer_output(right, left, 12, P_M_MV508_TREBLE, 0);
			break;

		case SOUND_MIXER_SYNTH:	/* Internal synthesizer (0-31) */
			levels[whichDev] = mixer_output(right, left, 31, P_M_MV508_MIXER | P_M_MV508_FM, mixer);
			break;
		case SOUND_MIXER_PCM:		/* PAS PCM (0-31) */
			levels[whichDev] = mixer_output(right, left, 31, P_M_MV508_MIXER | P_M_MV508_PCM, mixer);
			break;
		case SOUND_MIXER_ALTPCM:	/* SB PCM (0-31) */
			levels[whichDev] = mixer_output(right, left, 31, P_M_MV508_MIXER | P_M_MV508_SB, mixer);
			break;
		case SOUND_MIXER_SPEAKER:	/* PC speaker (0-31) */
			levels[whichDev] = mixer_output(right, left, 31, P_M_MV508_MIXER | P_M_MV508_SPEAKER, mixer);
			break;
		case SOUND_MIXER_LINE:	/* External line (0-31) */
			levels[whichDev] = mixer_output(right, left, 31, P_M_MV508_MIXER | P_M_MV508_LINE, mixer);
			break;
		case SOUND_MIXER_CD:	/* CD (0-31) */
			levels[whichDev] = mixer_output(right, left, 31, P_M_MV508_MIXER | P_M_MV508_CDROM, mixer);
			break;
		case SOUND_MIXER_MIC:		/* External microphone (0-31) */
			levels[whichDev] = mixer_output(right, left, 31, P_M_MV508_MIXER | P_M_MV508_MIC, mixer);
			break;
		case SOUND_MIXER_IMIX:		/* Recording monitor (0-31) (Only available on the Output Mixer) */
			levels[whichDev] = mixer_output(right, left, 31, P_M_MV508_MIXER | P_M_MV508_IMIXER,
									 P_M_MV508_OUTPUTMIX);
			break;
		case SOUND_MIXER_RECLEV:		/* Recording level (0-15) */
			levels[whichDev] = mixer_output(right, left, 15, P_M_MV508_MASTER_B, 0);
			break;

		case SOUND_MIXER_RECSRC:
			devmask = level & POSSIBLE_RECORDING_DEVICES;

			changed = devmask ^ rec_devices;
			rec_devices = devmask;

			for (i=0;i<SOUND_MIXER_NRDEVICES;i++)
			if (changed & (1<<i))
			{
				pas_mixer_set(i, levels[i]);
			}
			return rec_devices;
			break;

		default:
			return -EINVAL;
	}

	return(levels[whichDev]);
}

/*****/

static int
mixer_set_levels (struct sb_mixer_levels *user_l)
{
#define cmix(v) ((((v.r*100+7)/15)<<8)| ((v.l*100+7)/15))

  struct sb_mixer_levels l;
  memcpy_fromfs ((char *) &l, (char *) user_l, sizeof (l));

  if (l.master.l & ~0xF || l.master.r & ~0xF
      || l.line.l & ~0xF || l.line.r & ~0xF
      || l.voc.l & ~0xF || l.voc.r & ~0xF
      || l.fm.l & ~0xF || l.fm.r & ~0xF
      || l.cd.l & ~0xF || l.cd.r & ~0xF
      || l.mic & ~0x7)
    return (-EINVAL);

  pas_mixer_set(SOUND_MIXER_VOLUME, cmix(l.master));
  pas_mixer_set(SOUND_MIXER_LINE, cmix(l.line));
  pas_mixer_set(SOUND_MIXER_PCM, cmix(l.voc));
  pas_mixer_set(SOUND_MIXER_ALTPCM, cmix(l.voc));
  pas_mixer_set(SOUND_MIXER_SYNTH, cmix(l.fm));
  pas_mixer_set(SOUND_MIXER_CD, cmix(l.cd));
  pas_mixer_set(SOUND_MIXER_MIC, ((l.mic*100+3)/7)|(((l.mic*100+3)/7)<<8));
  return (0);
}

/* This sets aspects of the Mixer that are not volume levels. (Recording
   source, filter level, I/O filtering, and stereo.) */
static int
mixer_set_params (struct sb_mixer_params *user_p)
{
  struct sb_mixer_params p;
  BYTE val;
  int src;
  unsigned long flags;

  memcpy_fromfs ((char *) &p, (char *) user_p, sizeof (p));

  if (p.record_source != SRC_MIC
      && p.record_source != SRC_CD
      && p.record_source != SRC_LINE)
    return (-EINVAL);

  /* I'm not sure if this is The Right Thing.  Should stereo be entirely
     under control of DSP?  I like being able to toggle it while a sound is
     playing, so I do this... because I can. */

  DISABLE_INTR (flags);

  val = (pas_read (PCM_CONTROL) & ~P_C_MIXER_CROSS_FIELD) | P_C_MIXER_CROSS_R_TO_R | P_C_MIXER_CROSS_L_TO_L;
  if (!p.dsp_stereo)
    val |= (P_C_MIXER_CROSS_R_TO_L|P_C_MIXER_CROSS_L_TO_R);	/* Mono */
  pas_write (val, PCM_CONTROL);

  RESTORE_INTR (flags);

  switch (p.record_source)
  {
  case SRC_CD:
  	src = SOUND_MASK_CD;
  	break;

  case SRC_LINE:
  	src = SOUND_MASK_LINE;
  	break;

  default:
  	src = SOUND_MASK_MIC;
  	break;
  }

  pas_mixer_set(SOUND_MIXER_RECSRC, src);
  
  /* setmixer (OUT_FILTER, ((dsp_stereo ? STEREO_DAC : MONO_DAC) |
     (p.filter_output ? FILT_ON : FILT_OFF))); */
  return (0);
}

static int getmixer(int dev, int chn)
{
	if (chn == P_M_MV508_RIGHT)
	{
	  return (levels[dev] >> 8) & 0x7f;
	} else {
	  return levels[dev] & 0x7f;
	}
}

/* Read the current mixer level settings into the user's struct. */
static int
mixer_get_levels (struct sb_mixer_levels *user_l)
{

  struct sb_mixer_levels l;

  l.master.r = ((((levels[SOUND_MIXER_VOLUME] >> 8) & 0x7f) * 15)+50) / 100;	/* Master */
  l.master.l = (((levels[SOUND_MIXER_VOLUME] & 0x7f) * 15)+50) / 100;		/* Master */

  l.line.r = ((getmixer (SOUND_MIXER_LINE, P_M_MV508_RIGHT)  * 15)+50) / 100;	/* Line */
  l.line.l = ((getmixer (SOUND_MIXER_LINE, P_M_MV508_LEFT)  * 15)+50) / 100;

  l.voc.r = ((getmixer (SOUND_MIXER_PCM, P_M_MV508_RIGHT)  * 15)+50) / 100;	/* DAC */
  l.voc.l = ((getmixer (SOUND_MIXER_PCM, P_M_MV508_LEFT)  * 15)+50) / 100;

  l.fm.r = ((getmixer (SOUND_MIXER_SYNTH, P_M_MV508_RIGHT)  * 15)+50) / 100;	/* FM */
  l.fm.l = ((getmixer (SOUND_MIXER_SYNTH, P_M_MV508_LEFT)  * 15)+50) / 100;

  l.cd.r = ((getmixer (SOUND_MIXER_CD, P_M_MV508_RIGHT)  * 15)+50) / 100;	/* CD */
  l.cd.l = ((getmixer (SOUND_MIXER_CD, P_M_MV508_LEFT)  * 15)+50) / 100;

  l.mic = ((getmixer (SOUND_MIXER_MIC, P_M_MV508_LEFT) * 7)+50) / 100;	/* Microphone */

  memcpy_tofs ((char *) user_l, (char *) &l, sizeof (l));
  return (0);
}

/* Read the current mixer parameters into the user's struct. */
static int
mixer_get_params (struct sb_mixer_params *user_params)
{
  BYTE val;
  struct sb_mixer_params params;

  switch (rec_devices)
  {
  case SOUND_MASK_CD: 
      params.record_source = SRC_CD;
      break;

  case SOUND_MASK_LINE:
      params.record_source = SRC_LINE;
      break;

  case SOUND_MASK_MIC:
      params.record_source = SRC_MIC;
      break;

  default:
      params.record_source = SRC_MIC;
      pas_mixer_set(SOUND_MIXER_RECSRC, SOUND_MASK_MIC);	/* Adjust */
  }

  params.hifreq_filter = OFF;
  params.filter_input = OFF;
  params.filter_output = OFF;

  val = inb (PCM_CONTROL);
  params.dsp_stereo = ((val & P_C_MIXER_CROSS_FIELD) == (P_C_MIXER_CROSS_L_TO_L|P_C_MIXER_CROSS_R_TO_R));

  memcpy_tofs ((char *) user_params, (char *) &params, sizeof (params));
  return (0);
}

/*****/

static void pas_mixer_reset(void)
{
	int foo;

	TRACE(printk("pas2_mixer.c: void pas_mixer_reset(void)\n"));

	for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++)
		pas_mixer_set(foo, levels[foo]);

	pas_write(P_M_MV508_ADDRESS | P_M_MV508_MASTER_B, PARALLEL_MIXER);
	pas_write(15, PARALLEL_MIXER);

	pas_write(P_M_MV508_ADDRESS | P_M_MV508_MODE, PARALLEL_MIXER);
	pas_write(P_M_MV508_LOUDNESS | P_M_MV508_ENHANCE_40, PARALLEL_MIXER);	
}

int pas_mixer_ioctl(int dev, unsigned int cmd, unsigned int arg)
{
	TRACE(printk("pas2_mixer.c: int pas_mixer_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg));

	if ((cmd & (SOUND_BASE | SOUND_MIXER)) == (SOUND_BASE | SOUND_MIXER)) {
		if (cmd & SOUND_WRITE) 
			return pas_mixer_set(cmd & 0xff, arg);
		else {	/* Read parameters */

		  switch (cmd & 0xff) {

		  case SOUND_MIXER_RECSRC:
		  	return rec_devices;
		  	break;

		  case SOUND_MIXER_STEREODEVS:
		  	return SUPPORTED_MIXER_DEVICES & ~(SOUND_MASK_BASS|SOUND_MASK_TREBLE);
		  	break;

		  case SOUND_MIXER_DEVMASK:
		  	return SUPPORTED_MIXER_DEVICES;
		  	break;

		  case SOUND_MIXER_RECMASK:
		  	return POSSIBLE_RECORDING_DEVICES & SUPPORTED_MIXER_DEVICES;
		  	break;

		  case SOUND_MIXER_CAPS:
			return 0;	/* No special capabilities */
			break;

		  default:
			return levels[cmd & 0xff];
		  }
		}
	} else {
	  switch (cmd)
	    {
	    case MIXER_IOCTL_SET_LEVELS:
	      mixer_set_levels ((struct sb_mixer_levels *) arg);
	      return (mixer_get_levels ((struct sb_mixer_levels *) arg));
	    case MIXER_IOCTL_SET_PARAMS:
	      mixer_set_params ((struct sb_mixer_params *) arg);
	      return (mixer_get_params ((struct sb_mixer_params *) arg));
	    case MIXER_IOCTL_READ_LEVELS:
	      return (mixer_get_levels ((struct sb_mixer_levels *) arg));
	    case MIXER_IOCTL_READ_PARAMS:
	      return (mixer_get_params ((struct sb_mixer_params *) arg));
	    case MIXER_IOCTL_RESET:
	      pas_mixer_reset ();
	      return (0);
	    default:
	      return -EINVAL;
	    }
	}
	return -EINVAL;
}

static struct mixer_operations pas_mixer_operations =
{
	pas_mixer_ioctl
};

int
pas_init_mixer (void)
{
  pas_mixer_reset ();

  mixer_devs[num_mixers++] = &pas_mixer_operations;
  return 1;
}
#endif
