/*
 * linux/kernel/chr_drv/pcsp/pcsp_mixer.c
 * 
 * /dev/pcsp implementation - simple Mixer routines
 * 
 * (C) 1993, 1994  Michael Beck
 * Craig Metz (cmetz@thor.tjhsst.edu)
 */

#include "local.h"

#ifdef PCSP_MIXER

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/pcsp.h>

#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <asm/irq.h>

#define IOCTL_FROM_USER(d, s, o, c)     memcpy_fromfs((d), &((s)[o]), (c))
#define IOCTL_TO_USER(d, o, s, c)       memcpy_tofs(&((d)[o]), (s), (c))
#define IOCTL_IN(arg)                   get_fs_long((long *)(arg))
#define IOCTL_OUT(arg, ret)             (put_fs_long(ret, (long *)arg), 0)
#define OFF				0

static int      rec_devices = (0);	/* No recording source */

static struct {
	char		active;
} pcmixer;

#define POSSIBLE_RECORDING_DEVICES	(0)
#define SUPPORTED_MIXER_DEVICES		(SOUND_MASK_VOLUME)

static unsigned short levels[SOUND_MIXER_NRDEVICES] =
{
  (DEFAULT_RIGHT << 8) + DEFAULT_LEFT,	/* Master Volume */
  /* The next devices are not supported, so they are zero */
  0,				/* Bass */
  0,				/* Treble */
  0,				/* FM */
  0,				/* PCM */
  0,				/* PC Speaker */
  0,				/* Ext Line */
  0,				/* Mic */
  0,				/* CD */
  0,				/* Recording monitor */
  0,				/* SB PCM */
  0				/* Recording level */
};

extern void pcsp_set_volume(unsigned short);
extern unsigned pcsp_get_mode(void);

int pcsp_mixer_set(int whichDev, unsigned int level)
{
  int left, right;

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

  switch (whichDev)
    {
    case SOUND_MIXER_VOLUME:	/* Master volume (0-127) */
      levels[whichDev] = left | (right << 8);
      pcsp_set_volume(levels[whichDev]);
      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;

  IOCTL_FROM_USER((char *) &l, (char *) user_l, 0, 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);

  /* We only set the Master-volume, so the mixer is degenerate */
  pcsp_mixer_set(SOUND_MIXER_VOLUME, cmix (l.master));
  return (0);
}

/* 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 = 0;	/* Line */
  l.line.l = 0;

  l.voc.r = 0;	/* DAC */
  l.voc.l = 0;

  l.fm.r = 0;	/* FM */
  l.fm.l = 0;

  l.cd.r = 0;	/* CD */
  l.cd.l = 0;

  l.mic = 0;	/* Microphone */

  IOCTL_TO_USER((char *) user_l, 0, (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)
{
  struct sb_mixer_params params;

  params.record_source = SRC_MIC;
  params.hifreq_filter = OFF;
  params.filter_input  = OFF;
  params.filter_output = OFF;
  params.dsp_stereo    = pcsp_get_mode();

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

int pcsp_mixer_ioctl(struct inode * inode, struct file * file,
                        unsigned int cmd, unsigned long arg)
{

  if (((cmd >> 8) & 0xff) == 'M')
    {
      if (cmd & IOC_IN)
	return IOCTL_OUT(arg, pcsp_mixer_set(cmd & 0xff, IOCTL_IN (arg)));
      else
	{			/* Read parameters */

	  switch (cmd & 0xff)
	    {

	    case SOUND_MIXER_RECSRC:
	      return IOCTL_OUT(arg, rec_devices);
	      break;

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

	    case SOUND_MIXER_DEVMASK:
	      return IOCTL_OUT(arg, SUPPORTED_MIXER_DEVICES);
	      break;

	    case SOUND_MIXER_RECMASK:
	      return IOCTL_OUT(arg, POSSIBLE_RECORDING_DEVICES & SUPPORTED_MIXER_DEVICES);
	      break;

	    case SOUND_MIXER_CAPS:
	    case SOUND_MIXER_MUTE:
	    case SOUND_MIXER_ENHANCE:
	    case SOUND_MIXER_LOUD:
	      return IOCTL_OUT(arg, 0);
	      break;


	    default:
	      return IOCTL_OUT(arg, 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:
	  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:
          pcsp_mixer_set(SOUND_MIXER_VOLUME, levels[SOUND_MIXER_VOLUME]);
	  return (0);
	default:
	  return (-EINVAL);
	}
    }
  return (-EINVAL);
}

static void pcsp_mixer_release(struct inode * inode, struct file * file)
{
        --pcmixer.active;
}

static int pcsp_mixer_open(struct inode * inode, struct file * file)
{
	if (pcmixer.active)
		return (-EBUSY);
        ++pcmixer.active;
	return (0);
}

static int pcsp_mixer_inval(struct inode * inode, struct file * file, char * buffer, int count)
{
        return (-EINVAL);
}

struct file_operations pcsp_mixer_fops = {
        NULL,           /* pcsp_seek */
        pcsp_mixer_inval,
        pcsp_mixer_inval,
        NULL,           /* pcsp_readdir */
        NULL,           /* pcsp_select */
        pcsp_mixer_ioctl,
        NULL,           /* pcsp_mmap */
        pcsp_mixer_open,
        pcsp_mixer_release,
};

long pcsp_mixer_init(long kmem_start)
{
  pcsp_mixer_set(SOUND_MIXER_VOLUME, levels[SOUND_MIXER_VOLUME]);
  pcmixer.active = 0;
  return kmem_start;
}

#endif
