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

The low level driver for the SoundBlaster DS chips.

(C) 1992  Hannu Savolainen (hsavolai@cs.helsinki.fi)

The mixer support is based on the SB-BSD 1.5 driver by (C) Steve Haehnichen
   <shaehnic@ucsd.edu> */

#include "sound_config.h"

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

#undef SB_TEST_IRQ

#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"

#define OUTB outb
#define DEB(WHAT)		/* (WHAT) */
#define DEB1(WHAT)		/* (WHAT) */

#define DSP_RESET	(SBC_IO_BASE + 0x6)
#define DSP_READ	(SBC_IO_BASE + 0xA)
#define DSP_WRITE	(SBC_IO_BASE + 0xC)
#define DSP_COMMAND	(SBC_IO_BASE + 0xC)
#define DSP_STATUS	(SBC_IO_BASE + 0xC)
#define DSP_DATA_AVAIL	(SBC_IO_BASE + 0xE)
#define MIXER_ADDR	(SBC_IO_BASE + 0x4)
#define MIXER_DATA	(SBC_IO_BASE + 0x5)


#define POSSIBLE_RECORDING_DEVICES	(SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD)

#define SUPPORTED_MIXER_DEVICES		(SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | \
					 SOUND_MASK_CD | SOUND_MASK_VOLUME)

/* Mixer registers

NOTE!	RECORD_SRC == IN_FILTER */

#define VOC_VOL		0x04
#define MIC_VOL		0x0A
#define MIC_MIX		0x0A
#define RECORD_SRC	0x0C
#define IN_FILTER	0x0C
#define OUT_FILTER	0x0E
#define MASTER_VOL	0x22
#define FM_VOL		0x26
#define CD_VOL		0x28
#define LINE_VOL	0x2E

#define FREQ_HI         (1 << 3)/* Use High-frequency ANFI filters */
#define FREQ_LOW        0	/* Use Low-frequency ANFI filters */
#define FILT_ON         0	/* Yes, 0 to turn it on, 1 for off */
#define FILT_OFF        (1 << 5)

/* Convenient byte masks */
#define B1(x)	((x) & 0x01)
#define B2(x)	((x) & 0x03)
#define B3(x)	((x) & 0x07)
#define B4(x)	((x) & 0x0f)
#define B5(x)	((x) & 0x1f)
#define B6(x)	((x) & 0x3f)
#define B7(x)	((x) & 0x7f)
#define B8(x)	((x) & 0xff)
#define F(x)	(!!(x))		/* 0 or 1 only */

#define MONO_DAC	0x00
#define STEREO_DAC	0x02

/* DSP Commands */

#define DSP_CMD_SPKON		0xD1
#define DSP_CMD_SPKOFF		0xD3

/* The DSP channel can be used either for input or output. Variable
   'irq_mode' will be set when the program calls read or write first time
   after open. Current version doesn't support mode changes without closing
   and reopening the device. Support for this feature may be implemented in a
   future version of this driver. */

#define IMODE_NONE		0
#define IMODE_OUTPUT		1
#define IMODE_INPUT		2
#define IMODE_INIT		3
#define IMODE_MIDI		4

#define DMA_ADDR		0x002	/* Port for low 16 bits of DMA
					   address */
#define DMA_TOP			0x083	/* Port for top 4 bits */

#define DMA_COUNT		0x003	/* Port for DMA count */
#define DMA_M2			0x00C	/* DMA status */
#define DMA_M1			0x00B	/* DMA status */
#define DMA_INIT		0x00A	/* DMA init port */
#define DMA_RESET_VAL		0x005

#define NORMAL_MIDI	0
#define UART_MIDI	1

/* DISABLE_INTR and ENABLE_INTR are used to disable or enable interrupts.
   These macros store the current flags to the (unsigned long) variable given
   as a parameter. RESTORE_INTR returns the interrupt ebable bit to state
   before DISABLE_INTR or ENABLE_INTR */

#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));

static int sb_dsp_ok = 0;	/* Set to 1 after successful initialization */
static int midi_disabled = 0;
static int dsp_highspeed = 0, dsp_stereo = 0;
static int dsp_current_speed = DSP_DEFAULT_SPEED;

#ifndef EXCLUDE_SBPRO
static int rec_devices = SOUND_MASK_MIC;
static int hi_filter = 0, filter_in = 0, filter_out = 0;
#endif

static int midi_mode = NORMAL_MIDI;
static int midi_busy = 0;	/* 1 if the process has output to MIDI */
static int dsp_busy  = 0;

static int irq_mode = IMODE_NONE;	/* IMODE_INPUT, IMODE_OUTPUT or
					   IMODE_NONE */
static volatile int irq_ok = 0;

static int dsp_model = 1;	/* 1=SB, 2=SB Pro */
static int my_dev = 0;

static volatile int intr_active = 0;

static int dsp_speed (int);
static int dsp_set_stereo (int mode);
static int dsp_command (unsigned char val);
static void sb_dsp_output_block (int dev, char *buf, int count);

#ifndef EXCLUDE_SBPRO
static void setmixer (unsigned char port, unsigned char value);
static int getmixer (unsigned char port);
static void init_mixer ();
static int detect_mixer (void);
#endif

#if !defined(EXCLUDE_MIDI) || !defined(EXCLUDE_AUDIO)

/* Common code for the midi and pcm functions */

static int
dsp_command (unsigned char val)
{
  int i, limit;

  limit = jiffies + 10;		/* The timeout is 0.1 secods */

  /* Note! the i<5000000 is an emergency exit. The dsp_command() is sometimes
     called while interrupts are disabled. This means that the timer is
     disabled also. However the timeout situation is a abnormal condition.
     Normally the DSP should be ready to accept commands after just couple of
     loops. */

  for (i = 0; i < 5000000 && jiffies < limit; i++)
    {
      if ((inb (DSP_STATUS) & 0x80) == 0)
	{
	  OUTB (val, DSP_COMMAND);
	  return 1;
	}
    }

  printk ("SoundBlaster: DSP Command(%02x) Timeout.\n", val);
  return 0;
}

static void
sb_dsp_interrupt (int unused)
{
  int status, data;

  status = inb (DSP_DATA_AVAIL);/* Clear interrupt */

  if (intr_active)
    switch (irq_mode)
      {
      case IMODE_OUTPUT:
	intr_active = 0;
	DMAbuf_outputintr (my_dev);
	break;

      case IMODE_INPUT:
	intr_active = 0;
	DMAbuf_inputintr (my_dev);
	/* A complete buffer has been input. Let's start new one */
	break;

      case IMODE_INIT:
	printk ("SoundBlaster: IRQ test passed.\n");
	intr_active = 0;
	irq_ok = 1;
	break;

      case IMODE_MIDI:
        printk("+");
        data = inb(DSP_READ);
        printk("%02x", data);

        break;

      default:
	printk ("SoundBlaster: Unexpected interrupt\n");
      }
}

static int
set_dsp_irq (int interrupt_level)
{
  int retcode;
  struct sigaction sa;

  sa.sa_handler = sb_dsp_interrupt;

#ifdef SND_SA_INTERRUPT
  sa.sa_flags = SA_INTERRUPT;
#else
  sa.sa_flags = 0;
#endif

  sa.sa_mask = 0;
  sa.sa_restorer = NULL;

  retcode = irqaction (interrupt_level, &sa);

  if (retcode < 0)
    {
      printk ("SoundBlaster: IRQ%d already in use\n", interrupt_level);
    }

  return retcode;
}

static int
reset_dsp ()
{
  int loopc;

  OUTB (1, DSP_RESET);
  tenmicrosec ();
  OUTB (0, DSP_RESET);
  tenmicrosec ();
  tenmicrosec ();
  tenmicrosec ();

  for (loopc = 0; loopc < 1000 && !(inb (DSP_DATA_AVAIL) & 0x80); loopc++);	/* Wait for data
										   available status */

  if (inb (DSP_READ) != 0xAA)
    return 0;			/* Sorry */

  return 1;
}
#endif

#ifndef EXCLUDE_AUDIO

static void
dsp_speaker (char state)
{
  if (state)
    dsp_command (DSP_CMD_SPKON);
  else
    dsp_command (DSP_CMD_SPKOFF);
}

static int
dsp_speed (int speed)
{
  unsigned char tconst;
  unsigned long flags;


  if (speed < 4000)
    speed = 4000;

  if (speed > 44100)
    speed = 44100;		/* Invalid speed */

  if (dsp_model == 1 && speed > 22050)
    speed = 22050;
  /* SB Classic doesn't support higher speed */


  if (dsp_stereo && speed > 22050)
    speed = 22050;
  /* Max. stereo speed is 22050 */

  if ((speed > 22050) && midi_busy)
    {
      printk ("SB Warning: High speed DSP not possible simultaneously with MIDI output\n");
      speed = 22050;
    }

  if (dsp_stereo)
    speed <<= 1;

  /* Now the speed should be valid */

  if (speed > 22050)
    {				/* High speed mode */
      tconst = (unsigned char) ((65536 - (256000000 / speed)) >> 8);
      dsp_highspeed = 1;

      DISABLE_INTR (flags);
      if (dsp_command (0x40))
	dsp_command (tconst);
      RESTORE_INTR (flags);

      speed = (256000000 / (65536 - (tconst << 8)));
    }
  else
    {
      dsp_highspeed = 0;
      tconst = (256 - (1000000 / speed)) & 0xff;

      DISABLE_INTR (flags);
      if (dsp_command (0x40))	/* Set time constant */
	dsp_command (tconst);
      RESTORE_INTR (flags);

      speed = 1000000 / (256 - tconst);
    }

  if (dsp_stereo)
    speed >>= 1;

  dsp_current_speed = speed;
  return speed;
}

static int
dsp_set_stereo (int mode)
{
  dsp_stereo = 0;

  if (dsp_model == 1)
    return 0;			/* Sorry no stereo */

  if (mode && midi_busy)
    {
      printk ("SB Warning: Stereo DSP not possible simultaneously with MIDI output\n");
      return 0;
    }

  dsp_stereo = !!mode;

#ifndef EXCLUDE_SBPRO
  setmixer (OUT_FILTER, ((getmixer (OUT_FILTER) & ~STEREO_DAC)
			 | (mode ? STEREO_DAC : MONO_DAC)));
#endif
  dsp_speed(dsp_current_speed);	/* Speed must be recalculated if #channels changes */
  return mode;
}

static void
sb_dsp_output_block (int dev, char *buf, int count)
{
  unsigned long flags;

  if (!irq_mode)
    dsp_speaker (ON);

  irq_mode = IMODE_OUTPUT;
  DMAbuf_start_dma (1, (long) buf, count, DMA_WRITE);

  count--;

  if (dsp_highspeed)
    {
      DISABLE_INTR (flags);
      if (dsp_command (0x48))	/* High speed size */
	{
	  dsp_command (count & 0xff);
	  dsp_command ((count >> 8) & 0xff);
	  dsp_command (0x91);	/* High speed 8 bit DAC */
	}
      else
	printk ("SB Error: Unable to start (high speed) DAC\n");
      RESTORE_INTR (flags);
    }
  else
    {
      DISABLE_INTR (flags);
      if (dsp_command (0x14))	/* 8-bit DAC (DMA) */
	{
	  dsp_command (count & 0xff);
	  dsp_command ((count >> 8) & 0xff);
	}
      else
	printk ("SB Error: Unable to start DAC\n");
      RESTORE_INTR (flags);
    }
  intr_active = 1;
}

static void
sb_dsp_start_input (int dev, char *buf, int count)
{
  /* Start a DMA input to the buffer pointed by dmaqtail */

  unsigned long flags;

  if (!irq_mode)
    dsp_speaker (OFF);

  irq_mode = IMODE_INPUT;
  DMAbuf_start_dma (1, (long) buf, count, DMA_READ);

  count--;

  if (dsp_highspeed)
    {
      DISABLE_INTR (flags);
      if (dsp_command (0x48))	/* High speed size */
	{
	  dsp_command (count & 0xff);
	  dsp_command ((count >> 8) & 0xff);
	  dsp_command (0x99);	/* High speed 8 bit ADC */
	}
      else
	printk ("SB Error: Unable to start (high speed) ADC\n");
      RESTORE_INTR (flags);
    }
  else
    {
      DISABLE_INTR (flags);
      if (dsp_command (0x24))	/* 8-bit ADC (DMA) */
	{
	  dsp_command (count & 0xff);
	  dsp_command ((count >> 8) & 0xff);
	}
      else
	printk ("SB Error: Unable to start ADC\n");
      RESTORE_INTR (flags);
    }

  intr_active = 1;
}

static void
dsp_cleanup (void)
{
  intr_active = 0;
}

static int
sb_dsp_prepare_for_input (void)
{
  dsp_cleanup ();
  dsp_speaker (OFF);
  return 0;
}

static int
sb_dsp_prepare_for_output (void)
{
  dsp_cleanup ();
  dsp_speaker (ON);
  return 0;
}

static int
sb_dsp_open (int dev, int mode)
{
  int retval;

  if (!sb_dsp_ok)
    {
      printk ("SB Error: SoundBlaster board not installed\n");
      return -ENODEV;
    }

  if (!irq_ok)
    {
      printk ("SB Error: Incorrect IRQ setting (%d)\n", SBC_IRQ);
      return -ENODEV;
    }

  if (intr_active || (midi_busy && midi_mode == UART_MIDI))
    return -EBUSY;

  if (mode != O_RDONLY && mode != O_WRONLY)
    {
      printk ("SoundBlaster error: DAC and ACD not possible simultaneously\n");
      return -EINVAL;
    }

  retval = set_dsp_irq (SBC_IRQ);
  if (retval)
    return retval;

  if (!DMAbuf_open_dma (1))
    {
      free_irq (SBC_IRQ);
      return -EBUSY;
    }

  dsp_set_stereo (OFF);
  dsp_speed (DSP_DEFAULT_SPEED);
  irq_mode = IMODE_NONE;

  dsp_busy = 1;

  return 0;
}

static void
sb_dsp_close (int dev)
{
  DMAbuf_close_dma (1);
  free_irq (SBC_IRQ);
  dsp_cleanup ();
  dsp_speed (DSP_DEFAULT_SPEED);
  dsp_set_stereo (OFF);
  dsp_speaker (OFF);
  dsp_busy = 0;
}

static int
sb_dsp_ioctl (int dev, unsigned int cmd, unsigned int arg)
{
	if ((cmd & (SOUND_BASE | SOUND_PCM)) == (SOUND_BASE | SOUND_PCM)) {
	switch (cmd & SOUND_FUNC) {
		case SOUND_RATE | SOUND_WRITE:
			return dsp_speed(arg);

		case SOUND_RATE | SOUND_READ:
			return dsp_current_speed;

		case SOUND_CHANNELS | SOUND_WRITE:
			return dsp_set_stereo(arg-1) + 1;

		case SOUND_CHANNELS | SOUND_READ:
			return dsp_stereo + 1;

		case SOUND_BITS | SOUND_WRITE:
			return 8;	/* Sorry */

		case SOUND_BITS | SOUND_READ:
			return 8;

		case SOUND_FILTER | SOUND_WRITE:
		case SOUND_FILTER | SOUND_READ:
			return -EINVAL;
	 }
	} else {	/* Old style ioctl */
	  switch (cmd)
	    {
	      case SNDCTL_DSP_SPEED:
	      return dsp_speed (arg);
	      break;
	
	    case SNDCTL_DSP_STEREO:
	      return dsp_set_stereo (arg);
	      break;
	
	    case SNDCTL_DSP_SAMPLESIZE:
	      return 8;			/* Only 8 bits/sample supported */
	      break;
	
	    default:
	      return -EINVAL;
	    }
	}

	return -EINVAL;
}

static void
sb_dsp_reset (int dev)
{
  unsigned long flags;

  DISABLE_INTR (flags);

  reset_dsp ();
  dsp_cleanup ();

  RESTORE_INTR (flags);
}

#endif

int
sb_dsp_detect (void)
{
  if (sb_dsp_ok)
    return 0;			/* Already initialized */

  irq_ok = 0;

  if (!reset_dsp ())
    return 0;

#ifdef SB_TEST_IRQ
  /* Test the IRQ -address (output two bytes and wait for interrupt) */
  /* Note! This routine outputs the byte from physical offset 0 */

  irq_mode = IMODE_INIT;

  if ((ret = set_dsp_irq (SBC_IRQ)) < 0)
    return 0;			/* IRQ not free */

  if (!DMAbuf_open_dma (1))
    {
      free_irq (SBC_IRQ);
      return 0;
    }

  dsp_set_stereo (OFF);
  dsp_speed (DSP_DEFAULT_SPEED);
  dsp_speaker (ON);

  DMAbuf_start_dma (1, 0, 2, DMA_WRITE);

  if (dsp_command (0x14))	/* 8-bit DAC (DMA) */
    {
      dsp_command (1);		/* Two bytes */
      dsp_command (0);
    }
  else
    printk ("SB Error: Unable to start DAC\n");

  /* Busy wait until interrupt or timeout occurs */
  /* IRQ handler sets irq_ok */

  jif = jiffies;
  while (!irq_ok && ((jiffies - jif) < 3));

  DMAbuf_close_dma (1);
  free_irq (SBC_IRQ);
  intr_active = 0;
  irq_mode = IMODE_NONE;
  dsp_speaker (OFF);

  if (!irq_ok)			/* Interrupt not detected */
    {
      printk ("SoundBlaster: Incorrect SBC_IRQ setting (%d)\n", SBC_IRQ);
      return 0;
    }

  DEB (printk ("SoundBlaster: IRQ%d OK\n", SBC_IRQ));
#else
  irq_ok = 1;
#endif

  sb_dsp_ok = 1;
  return 1;			/* Detected */
}

#ifndef EXCLUDE_SBPRO

static void
setmixer (unsigned char port, unsigned char value)
{
  OUTB (port, MIXER_ADDR);	/* Select register */
  tenmicrosec ();
  OUTB (value, MIXER_DATA);
  tenmicrosec ();
}

static int
getmixer (unsigned char port)
{
  int val;
  OUTB (port, MIXER_ADDR);	/* Select register */
  tenmicrosec ();
  val = inb (MIXER_DATA);
  tenmicrosec ();

  return val;
}

static int
detect_mixer (void)
{
  setmixer (FM_VOL, 0xff);
  setmixer (VOC_VOL, 0x33);

  if (getmixer (FM_VOL) != 0xff)
    return 0;			/* No match */
  if (getmixer (VOC_VOL) != 0x33)
    return 0;

  return 1;
}

static void
init_mixer ()
{
  setmixer (MASTER_VOL, 0xbb);
  setmixer (VOC_VOL, 0x99);
  setmixer (LINE_VOL, 0xbb);
  setmixer (FM_VOL, 0x99);
  setmixer (CD_VOL, 0x11);
  setmixer (MIC_MIX, 0x11);
  setmixer (RECORD_SRC, 0x31);
  setmixer (OUT_FILTER, 0x31);
}

static void
set_filter(int record_source, int hifreq_filter, int filter_input, int filter_output)
{
  setmixer (RECORD_SRC, (record_source
			 | (hifreq_filter ? FREQ_HI : FREQ_LOW)
			 | (filter_input ? FILT_ON : FILT_OFF)));

  setmixer (OUT_FILTER, ((dsp_stereo ? STEREO_DAC : MONO_DAC)
			 | (filter_output ? FILT_ON : FILT_OFF)));

  hi_filter = hifreq_filter;
  filter_in = filter_input;
  filter_out = filter_output;
}

static int mixer_output(int right_vol, int left_vol, int div, int device)
{
	int left = ((left_vol * div) + 50) / 100;
	int right = ((right_vol * div) + 50) / 100;

	setmixer(device, ((left & 0xf) << 4) | (right & 0xf));

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

static int sbp_mixer_set(int whichDev, unsigned int level)
{
	int left, right, devmask;

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

	switch(whichDev) {
		case SOUND_MIXER_VOLUME:	/* Master volume (0-15) */
			return mixer_output(right, left, 15, MASTER_VOL);
			break;
		case SOUND_MIXER_SYNTH:	/* Internal synthesizer (0-15) */
			return mixer_output(right, left, 15, FM_VOL);
			break;
		case SOUND_MIXER_PCM:		/* PAS PCM (0-15) */
			return mixer_output(right, left, 15, VOC_VOL);
			break;
		case SOUND_MIXER_LINE:	/* External line (0-15) */
			return mixer_output(right, left, 15, LINE_VOL);
			break;
		case SOUND_MIXER_CD:	/* CD (0-15) */
			return mixer_output(right, left, 15, CD_VOL);
			break;
		case SOUND_MIXER_MIC:		/* External microphone (0-7) */
			return mixer_output(right, left, 7, MIC_VOL);
			break;

		case SOUND_MIXER_RECSRC:
			devmask = level & POSSIBLE_RECORDING_DEVICES;

			if (devmask != SOUND_MASK_MIC	&&
			    devmask != SOUND_MASK_LINE	&&
			    devmask != SOUND_MASK_CD)
			{	/* More than one devices selected. Drop the previous selection */
				devmask &= ~rec_devices;
			}

			if (devmask != SOUND_MASK_MIC	&&
			    devmask != SOUND_MASK_LINE	&&
			    devmask != SOUND_MASK_CD)
			{	/* More than one devices selected. Default to mic */
				devmask = SOUND_MASK_MIC;
			}

			if (devmask ^ rec_devices)	/* Input source changed */
			{
				switch (devmask) {

				case SOUND_MASK_MIC:
 				   set_filter(SRC_MIC, hi_filter, filter_in, filter_out);
				   break;

				case SOUND_MASK_LINE:
 				   set_filter(SRC_LINE, hi_filter, filter_in, filter_out);
				   break;

				case SOUND_MASK_CD:
 				   set_filter(SRC_CD, hi_filter, filter_in, filter_out);
				   break;

				default:
 				   set_filter(SRC_MIC, hi_filter, filter_in, filter_out);
				}
			}

			rec_devices = devmask;

			return rec_devices;
			break;

		default:
			return -EINVAL;
	}

}

static int mixer_input(int div, int device)
{
	int level, left, right, half;

	level = getmixer(device);
	half = div / 2;

	left = ((((level & 0xf0) >> 4)	* 100) + half) / div;
	right = (((level & 0x0f)	* 100) + half) / div;

	return (right << 8) | left;
}

static int sbp_mixer_get(int whichDev)
{

	switch(whichDev) {
		case SOUND_MIXER_VOLUME:	/* Master volume (0-15) */
			return mixer_input(15, MASTER_VOL);
			break;
		case SOUND_MIXER_SYNTH:	/* Internal synthesizer (0-15) */
			return mixer_input(15, FM_VOL);
			break;
		case SOUND_MIXER_PCM:		/* PAS PCM (0-15) */
			return mixer_input(15, VOC_VOL);
			break;
		case SOUND_MIXER_LINE:	/* External line (0-15) */
			return mixer_input(15, LINE_VOL);
			break;
		case SOUND_MIXER_CD:	/* CD (0-15) */
			return mixer_input(15, CD_VOL);
			break;
		case SOUND_MIXER_MIC:		/* External microphone (0-7) */
			return mixer_input(7, MIC_VOL);
			break;

		default:
			return -EINVAL;
		}

}

/* Sets mixer volume levels. All levels except mic are 0 to 15, mic is 7. See
   sbinfo.doc for details on granularity and such. Basically, the mixer
   forces the lowest bit high, effectively reducing the possible settings by
   one half.  Yes, that's right, volume levels have 8 settings, and
   microphone has four.  Sucks. */
static int
mixer_set_levels (struct sb_mixer_levels *user_l)
{
  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);

  setmixer (MASTER_VOL, (l.master.l << 4) | l.master.r);
  setmixer (LINE_VOL, (l.line.l << 4) | l.line.r);
  setmixer (VOC_VOL, (l.voc.l << 4) | l.voc.r);
  setmixer (FM_VOL, (l.fm.l << 4) | l.fm.r);
  setmixer (CD_VOL, (l.cd.l << 4) | l.cd.r);
  setmixer (MIC_VOL, l.mic);
  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;

  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. */

  dsp_stereo = !!p.dsp_stereo;

  set_filter(p.record_source, p.hifreq_filter, p.filter_input, p.filter_output);

  switch (p.record_source) {

  case SRC_MIC:
  	rec_devices = SOUND_MASK_MIC;
  	break;

  case SRC_LINE:
  	rec_devices = SOUND_MASK_LINE;
  	break;

  case SRC_CD:
  	rec_devices = SOUND_MASK_CD;
  }

  return (0);
}

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

  val = getmixer (MASTER_VOL);	/* Master */
  l.master.l = B4 (val >> 4);
  l.master.r = B4 (val);

  val = getmixer (LINE_VOL);	/* FM */
  l.line.l = B4 (val >> 4);
  l.line.r = B4 (val);

  val = getmixer (VOC_VOL);	/* DAC */
  l.voc.l = B4 (val >> 4);
  l.voc.r = B4 (val);

  val = getmixer (FM_VOL);	/* FM */
  l.fm.l = B4 (val >> 4);
  l.fm.r = B4 (val);

  val = getmixer (CD_VOL);	/* CD */
  l.cd.l = B4 (val >> 4);
  l.cd.r = B4 (val);

  val = getmixer (MIC_VOL);	/* Microphone */
  l.mic = B3 (val);

  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;

  val = getmixer (RECORD_SRC);
  params.record_source = val & 0x07;
  params.hifreq_filter = !!(val & FREQ_HI);
  params.filter_input = (val & FILT_OFF) ? OFF : ON;
  params.filter_output = (getmixer (OUT_FILTER) & FILT_OFF) ? OFF : ON;
  params.dsp_stereo = dsp_stereo;

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

static int
sb_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg)
{
	if ((cmd & (SOUND_BASE | SOUND_MIXER)) == (SOUND_BASE | SOUND_MIXER)) {
		if (cmd & SOUND_WRITE) 
			return sbp_mixer_set(cmd & 0xff, arg);
		else {	/* Read parameters */

		  switch (cmd & 0xff) {

		  case SOUND_MIXER_RECSRC:
		  	return rec_devices;
		  	break;

		  case SOUND_MIXER_DEVMASK:
		  	return SUPPORTED_MIXER_DEVICES;
		  	break;

		  case SOUND_MIXER_STEREODEVS:
		  	return SUPPORTED_MIXER_DEVICES & ~SOUND_MASK_MIC;
		  	break;

		  case SOUND_MIXER_RECMASK:
		  	return POSSIBLE_RECORDING_DEVICES;
		  	break;

		  case SOUND_MIXER_CAPS:
			return SOUND_CAP_EXCL_INPUT;
			break;

		  default:
			return sbp_mixer_get(cmd & 0xff);
		  }
		}
	} else {
	  switch (cmd)
	    {
	      case MIXER_IOCTL_SET_LEVELS:
	      return (mixer_set_levels ((struct sb_mixer_levels *) arg));
	    case MIXER_IOCTL_SET_PARAMS:
	      return (mixer_set_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:
	      init_mixer ();
	      return (0);
	    default:
	      return -EINVAL;
	    }
	}
}

/* End of mixer code */
#endif

#ifndef EXCLUDE_MIDI

/* Midi code */

static int
sb_midi_open (int dev, int mode)
{
  int ret;

  if (!sb_dsp_ok)
    {
      printk ("SB Error: MIDI hardware not installed\n");
      return -ENODEV;
    }

  if (mode != O_WRONLY /* && dsp_model !=2 *Don't work*/)
    {
      printk ("SoundBlaster: Midi input not currently supported\n");
      return -EPERM;
    }

  midi_mode = NORMAL_MIDI;
  if (mode != O_WRONLY)
    {
    	if (dsp_busy || intr_active) return -EBUSY;
    	midi_mode = UART_MIDI;
    }

  if (dsp_highspeed || dsp_stereo)
    {
      printk ("SB Error: Midi output not possible during stereo or high speed audio\n");
      return -EBUSY;
    }

  if (midi_mode == UART_MIDI)
  {
    irq_mode = IMODE_MIDI;

    reset_dsp();

    if (!dsp_command(0x35)) return -EIO;	/* Enter the UART mode */
    intr_active = 1;

    if ((ret = set_dsp_irq (SBC_IRQ)) < 0)
       return 0;			/* IRQ not free */
  }

  midi_busy = 1;

  return 0;
}

static void
sb_midi_close (int dev)
{
  if (midi_mode == UART_MIDI)
  {
  	reset_dsp();	/* The only way to kill the UART mode */
  	free_irq(SBC_IRQ);
  }
  intr_active = 0;
  midi_busy = 0;
}

static int
sb_midi_out (int dev, unsigned char midi_byte)
{
  unsigned long flags;

  midi_busy = 1;		/* Kill all notes after close */

  if (midi_mode == NORMAL_MIDI)
  {
  DISABLE_INTR (flags);
  if (dsp_command (0x38))
    dsp_command (midi_byte);
  else
    printk ("SB Error: Unable to send a MIDI byte\n");
  RESTORE_INTR (flags);
  } else dsp_command(midi_byte);	/* UART write */

  return 1;
}

static int
sb_midi_start_read (int dev)
{
  if (midi_mode != UART_MIDI)
  {
    printk ("SoundBlaster: MIDI input not implemented.\n");
    return -EPERM;
  }
  return 0;
}

static int
sb_midi_end_read (int dev)
{
  if (midi_mode == UART_MIDI)
  {
    reset_dsp();
    intr_active = 0;
  }
  return 0;
}

static int
sb_midi_ioctl (int dev, unsigned cmd, unsigned arg)
{
  return -EPERM;
}

/* End of midi code */
#endif

#ifndef EXCLUDE_AUDIO
static struct audio_operations sb_dsp_operations =
{
  sb_dsp_open,
  sb_dsp_close,
  sb_dsp_output_block,
  sb_dsp_start_input,
  sb_dsp_ioctl,
  sb_dsp_prepare_for_input,
  sb_dsp_prepare_for_output,
  sb_dsp_reset
};
#endif

#ifndef EXCLUDE_SBPRO
static struct mixer_operations sb_mixer_operations =
{
  sb_mixer_ioctl
};
#endif

#ifndef EXCLUDE_MIDI
static struct midi_operations sb_midi_operations =
{
  sb_midi_open,
  sb_midi_close,
  sb_midi_ioctl,
  sb_midi_out,
  sb_midi_start_read,
  sb_midi_end_read
};
#endif

long
sb_dsp_init (long mem_start)
{

#ifndef EXCLUDE_SBPRO
  if (detect_mixer ())
    {
      printk ("SoundBlaster Pro board detected, IRQ=%d\n", SBC_IRQ);
      init_mixer ();
      dsp_model = 2;
      mixer_devs[num_mixers++] = &sb_mixer_operations;
    }
  else
#endif
    printk ("SoundBlaster compatible DSP detected, IRQ=%d\n", SBC_IRQ);

#ifndef EXCLUDE_AUDIO
  if (num_dspdevs < MAX_DSP_DEV)
    dsp_devs[my_dev=num_dspdevs++] = &sb_dsp_operations;
  else
    printk ("Too many DSP devices detected\n");
#endif

#ifndef EXCLUDE_MIDI
  if (!midi_disabled)
    midi_devs[num_midis++] = &sb_midi_operations;
#endif


  return mem_start;
}

void
sb_dsp_disable_midi (void)
{
  midi_disabled = 1;
}

#endif
