/*
 * sound/gus_wave.c
 * 
 * Driver for the Gravis UltraSound wave table synth.
 * 
 * Copyright by Hannu Savolainen 1993, 1994
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer. 2.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 */

#include <stddef.h>

#include <i386/isa/sound/sound_config.h>
#include <i386/isa/sound/ultrasound.h>
#include <i386/isa/sound/gus_hw.h>
#include <i386/isa/sound/iwdefs.h>
#include <machine/clock.h>

/* PnP stuff */
#define GUS_PNP_ID 0x100561e

#define MAX_CARDS 8
#define MAX_GUS_PNP 12


/* Static ports */
#define PADDRESS	0x279
#define PWRITE_DATA	0xa79
#define SET_CSN			0x06
#define PSTATUS			0x05

/* PnP Registers.  Write to ADDRESS and then use WRITE/READ_DATA */
#define SET_RD_DATA		0x00
#define SERIAL_ISOLATION	0x01
#define WAKE			0x03

#if defined(CONFIG_GUS)

static IWAVE iw;
#define 	ENTER_CRITICAL

#define 	LEAVE_CRITICAL

#define MAX_SAMPLE	150
#define MAX_PATCH	256


static u_int gus_pnp_found[MAX_GUS_PNP] =
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

struct voice_info {
	u_long   orig_freq;
	u_long   current_freq;
	u_long   mode;
	int             bender;
	int             bender_range;
	int             panning;
	int             midi_volume;
	u_int    initial_volume;
	u_int    current_volume;
	int             loop_irq_mode, loop_irq_parm;
#define LMODE_FINISH		1
#define LMODE_PCM		2
#define LMODE_PCM_STOP		3
	int             volume_irq_mode, volume_irq_parm;
#define VMODE_HALT		1
#define VMODE_ENVELOPE		2
#define VMODE_START_NOTE	3

	int             env_phase;
	u_char   env_rate[6];
	u_char   env_offset[6];

	/*
	 * Volume computation parameters for gus_adagio_vol()
	 */
	int             main_vol, expression_vol, patch_vol;

	/* Variables for "Ultraclick" removal */
	int             dev_pending, note_pending, volume_pending, sample_pending;
	char            kill_pending;
	long            offset_pending;

};

static struct voice_alloc_info *voice_alloc;

extern int      gus_base;
extern int      gus_irq, gus_dma;
static int      gus_dma2 = -1;
static int      dual_dma_mode = 0;
static long     gus_mem_size = 0;
static long     free_mem_ptr = 0;
static int      gus_no_dma = 0;
static int      nr_voices = 0;
static int      gus_devnum = 0;
static int      volume_base, volume_scale, volume_method;
static int      gus_recmask = SOUND_MASK_MIC;
static int      recording_active = 0;
static int      only_read_access = 0;
static int      only_8_bits = 0;

int             gus_wave_volume = 60;
static int      gus_pcm_volume = 80;
int             have_gus_max = 0;
static int      gus_line_vol = 100, gus_mic_vol = 0;
static u_char mix_image = 0x00;

int             gus_timer_enabled = 0;
/*
 * Current version of this driver doesn't allow synth and PCM functions at
 * the same time. The active_device specifies the active driver
 */
static int      active_device = 0;

#define GUS_DEV_WAVE		1	/* Wave table synth */
#define GUS_DEV_PCM_DONE	2	/* PCM device, transfer done */
#define GUS_DEV_PCM_CONTINUE	3	/* PCM device, transfer done ch. 1/2 */

static int      gus_sampling_speed;
static int      gus_sampling_channels;
static int      gus_sampling_bits;

static int     *dram_sleeper = NULL;
static volatile struct snd_wait dram_sleep_flag =
{0};

/*
 * Variables and buffers for PCM output
 */
#define MAX_PCM_BUFFERS		(32*MAX_REALTIME_FACTOR)	/* Don't change */

static int      pcm_bsize, pcm_nblk, pcm_banksize;
static int      pcm_datasize[MAX_PCM_BUFFERS];
static volatile int pcm_head, pcm_tail, pcm_qlen;
static volatile int pcm_active;
static volatile int dma_active;
static int      pcm_opened = 0;
static int      pcm_current_dev;
static int      pcm_current_block;
static u_long pcm_current_buf;
static int      pcm_current_count;
static int      pcm_current_intrflag;

extern sound_os_info *gus_osp;

static struct voice_info voices[32];

static int      freq_div_table[] =
{
	44100,			/* 14 */
	41160,			/* 15 */
	38587,			/* 16 */
	36317,			/* 17 */
	34300,			/* 18 */
	32494,			/* 19 */
	30870,			/* 20 */
	29400,			/* 21 */
	28063,			/* 22 */
	26843,			/* 23 */
	25725,			/* 24 */
	24696,			/* 25 */
	23746,			/* 26 */
	22866,			/* 27 */
	22050,			/* 28 */
	21289,			/* 29 */
	20580,			/* 30 */
	19916,			/* 31 */
	19293			/* 32 */
};

static struct patch_info *samples;
static struct patch_info *dbg_samples;
static int                dbg_samplep;

static long     sample_ptrs[MAX_SAMPLE + 1];
static int      sample_map[32];
static int      free_sample;
static int      mixer_type = 0;


static int      patch_table[MAX_PATCH];
static int      patch_map[32];

static struct synth_info gus_info =
{"Gravis UltraSound", 0, SYNTH_TYPE_SAMPLE, SAMPLE_TYPE_GUS, 0, 16, 0, MAX_PATCH};

static void     gus_default_mixer_init(void);

static int      guswave_start_note2(int dev, int voice, int note_num, int volume);
static void     gus_poke(long addr, u_char data);
static void     compute_and_set_volume(int voice, int volume, int ramp_time);
extern u_short gus_adagio_vol(int vel, int mainv, int xpn, int voicev);
extern u_short gus_linear_vol(int vol, int mainvol);
static void     compute_volume(int voice, int volume);
static void     do_volume_irq(int voice);
static void     set_input_volumes(void);
static void     gus_tmr_install(int io_base);

static void     SEND(int d, int r);
static int      get_serial(int rd_port, u_char *data);
static void     send_Initiation_LFSR(void);
static int      isolation_protocol(int rd_port);


#define	INSTANT_RAMP		-1	/* Instant change. No ramping */
#define FAST_RAMP		0	/* Fastest possible ramp */


/* Crystal Select */
#define CODEC_XTAL2			0x01	/* 16.9344 crystal */
#define CODEC_XTAL1			0x00	/* 24.576 crystal */
/************************************************************************/

/************************************************************************/
/* Definitions for CONFIG_1 register                                    */
#define CODEC_CFIG1I_DEFAULT    0x03 | 0x8
#define CODEC_CAPTURE_PIO	0x80	/* Capture PIO enable */
#define CODEC_PLAYBACK_PIO	0x40	/* Playback PIO enable */
#define CODEC_AUTOCALIB		0x08	/* auto calibrate */
#define CODEC_SINGLE_DMA	0x04	/* Use single DMA channel */
#define CODEC_RE		0x02	/* Capture enable */
#define CODEC_PE		0x01	/* playback enable */
/************************************************************************/

/************************************************************************/
/* Definitions for CONFIG_2 register                                    */
#define CODEC_CFIG2I_DEFAULT    0x81
#define CODEC_OFVS 0x80		/* Output Full Scale Voltage */
#define CODEC_TE   0x40		/* Timer Enable */
#define CODEC_RSCD 0x20		/* Recors Sample Counter Disable */
#define CODEC_PSCD 0x10		/* Playback Sample Counter Disable */
#define CODEC_DAOF 0x01		/* D/A Ouput Force Enable */
/************************************************************************/

/************************************************************************/
/* Definitions for CONFIG_3 register                                    */
/* #define CODEC_CFIG3I_DEFAULT    0xe0  0x02 when synth DACs are working */

#define CODEC_CFIG3I_DEFAULT    0xc0	/* 0x02 when synth DACs are working */
#define CODEC_RPIE    0x80	/* Record FIFO IRQ Enable */
#define CODEC_PPIE    0x40	/* Playback FIFO IRQ Enable */
#define CODEC_FT_MASK 0x30	/* FIFO Threshold Select */
#define CODEC_PVFM    0x04	/* Playback Variable Frequency Mode */
#define CODEC_SYNA    0x02	/* AUX1/Synth Signal Select */
/************************************************************************/

/************************************************************************/
/* Definitions for EXTERNAL_CONTROL register                            */
#define CODEC_CEXTI_DEFAULT     0x00
#define CODEC_IRQ_ENABLE	0x02	/* interrupt enable */
#define CODEC_GPOUT1		0x80	/* external control #1 */
#define CODEC_GPOUT0		0x40	/* external control #0 */
/************************************************************************/

/************************************************************************/
/* Definitions for MODE_SELECT_ID register                              */
#define CODEC_MODE_DEFAULT 0x40
#define CODEC_MODE_MASK  0x60
#define CODEC_ID_BIT4    0x80
#define CODEC_ID_BIT3_0  0x0F
/************************************************************************/
#define CONFIG_1            0x09
#define EXTERNAL_CONTROL    0x0a/* Pin control */
#define STATUS_2            0x0b/* Test and initialization */
#define MODE_SELECT_ID      0x0c/* Miscellaneaous information */
#define LOOPBACK            0x0d/* Digital Mix */
#define UPPER_PLAY_COUNT    0x0e/* Playback Upper Base Count */
#define LOWER_PLAY_COUNT    0x0f/* Playback Lower Base Count */
#define CONFIG_2            0x10
#define CONFIG_3            0x11


#define IWL_CODEC_OUT(reg, val) \
    { outb(iwl_codec_base, reg); outb(iwl_codec_data, val); }

#define IWL_CODEC_IN(reg, val) \
    { outb(iwl_codec_base, reg); val = inb(iwl_codec_data); }


static u_char   gus_look8(int reg);

static void     gus_write16(int reg, u_int data);

static u_short  gus_read16(int reg);

static void     gus_write_addr(int reg, u_long address, int is16bit);
static void     IwaveLineLevel(char level, char index);
static void     IwaveInputSource(BYTE index, BYTE source);
static void     IwaveDelay(WORD count);
static void     IwaveStopDma(BYTE path);
static void     IwavePnpGetCfg(void);
static void     IwavePnpDevice(BYTE dev);
static void     IwavePnpSetCfg(void);
static void     IwavePnpKey(void);
static BYTE     IwavePnpIsol(PORT * pnpread);
static void     IwaveCfgIOSpace(void);

static void     IwavePnpSerial(PORT pnprdp, BYTE csn,
							   BYTE * vendor, DWORD * serial);


static void     IwavePnpPeek(PORT pnprdp, WORD bytes, BYTE * data);
static void     IwavePnpEeprom(BYTE ctrl);
static void     IwavePnpActivate(BYTE dev, BYTE bool);
				
static void     IwavePnpPower(BYTE mode);
static void     IwavePnpWake(BYTE csn);
static PORT     IwavePnpIOcheck(PORT base, BYTE no_ports);
				
static BYTE     IwavePnpGetCSN(DWORD VendorID, BYTE csn_max);
static BYTE     IwavePnpPing(DWORD VendorID);
static WORD     IwaveMemSize(void);
static BYTE     IwaveMemPeek(ADDRESS addr);
static void     IwaveMemPoke(ADDRESS addr, BYTE datum);
static void     IwaveMemCfg(DWORD * lpbanks);
static void     IwaveCodecIrq(BYTE mode);
static WORD     IwaveRegPeek(DWORD reg_mnem);
				
static void     IwaveRegPoke(DWORD reg_mnem, WORD datum);
static void     IwaveCodecMode(char mode);
static void     IwaveLineMute(BYTE mute, BYTE inx);
static void     Iwaveinitcodec(void);
int             IwaveOpen(char voices, char mode, struct address_info * hw);


static void
reset_sample_memory(void)
{
    int             i;

    for (i = 0; i <= MAX_SAMPLE; i++)
	sample_ptrs[i] = -1;
    for (i = 0; i < 32; i++)
	sample_map[i] = -1;
    for (i = 0; i < 32; i++)
	patch_map[i] = -1;

    gus_poke(0, 0);		/* Put a silent sample to the beginning */
    gus_poke(1, 0);
    free_mem_ptr = 2;

    free_sample = 0;

    for (i = 0; i < MAX_PATCH; i++)
	patch_table[i] = -1;
}

void
gus_delay(void)
{
    int             i;

    for (i = 0; i < 7; i++)
	inb(u_DRAMIO);
}

static void
gus_poke(long addr, u_char data)
{				/* Writes a byte to the DRAM */
    u_long   flags;

    flags = splhigh();
    outb(u_Command, 0x43);
    outb(u_DataLo, addr & 0xff);
    outb(u_DataHi, (addr >> 8) & 0xff);

    outb(u_Command, 0x44);
    outb(u_DataHi, (addr >> 16) & 0xff);
    outb(u_DRAMIO, data);
    splx(flags);
}

static u_char
gus_peek(long addr)
{				/* Reads a byte from the DRAM */
    u_long   flags;
    u_char   tmp;

    flags = splhigh();
    outb(u_Command, 0x43);
    outb(u_DataLo, addr & 0xff);
    outb(u_DataHi, (addr >> 8) & 0xff);

    outb(u_Command, 0x44);
    outb(u_DataHi, (addr >> 16) & 0xff);
    tmp = inb(u_DRAMIO);
    splx(flags);

    return tmp;
}

void
gus_write8(int reg, u_int data)
{				/* Writes to an indirect register (8 bit) */
    u_long   flags;

    flags = splhigh();
    outb(u_Command, reg);
    outb(u_DataHi, (u_char) (data & 0xff));
    splx(flags);
}

u_char
gus_read8(int reg)
{	/* Reads from an indirect register (8 bit). Offset 0x80. */
    u_long   flags;
    u_char   val;

    flags = splhigh();
    outb(u_Command, reg | 0x80);
    val = inb(u_DataHi);
    splx(flags);

    return val;
}

static u_char
gus_look8(int reg)
{	/* Reads from an indirect register (8 bit). No additional offset. */
    u_long   flags;
    u_char   val;

    flags = splhigh();
    outb(u_Command, reg);
    val = inb(u_DataHi);
    splx(flags);

    return val;
}

static void
gus_write16(int reg, u_int data)
{			/* Writes to an indirect register (16 bit) */
    u_long   flags;

    flags = splhigh();

    outb(u_Command, reg);

    outb(u_DataLo, (u_char) (data & 0xff));
    outb(u_DataHi, (u_char) ((data >> 8) & 0xff));

    splx(flags);
}

static u_short
gus_read16(int reg)
{		/* Reads from an indirect register (16 bit). Offset 0x80. */
    u_long   flags;
    u_char   hi, lo;

    flags = splhigh();

    outb(u_Command, reg | 0x80);

    lo = inb(u_DataLo);
    hi = inb(u_DataHi);

    splx(flags);

    return ((hi << 8) & 0xff00) | lo;
}

static void
gus_write_addr(int reg, u_long address, int is16bit)
{				/* Writes an 24 bit memory address */
    u_long   hold_address;
    u_long   flags;

    flags = splhigh();
    if (is16bit) {
	/*
	 * Special processing required for 16 bit patches
	 */

	hold_address = address;
	address = address >> 1;
	address &= 0x0001ffffL;
	address |= (hold_address & 0x000c0000L);
    }
    gus_write16(reg, (u_short) ((address >> 7) & 0xffff));
    gus_write16(reg + 1, (u_short) ((address << 9) & 0xffff));
    /*
     * Could writing twice fix problems with GUS_VOICE_POS() ? Lets try...
     */
    gus_delay();
    gus_write16(reg, (u_short) ((address >> 7) & 0xffff));
    gus_write16(reg + 1, (u_short) ((address << 9) & 0xffff));
    splx(flags);
}

static void
gus_select_voice(int voice)
{
    if (voice < 0 || voice > 31)
	return;

    outb(u_Voice, voice);
}

static void
gus_select_max_voices(int nvoices)
{
    if (nvoices < 14)
	nvoices = 14;
    if (nvoices > 32)
	nvoices = 32;

    voice_alloc->max_voice = nr_voices = nvoices;

    gus_write8(0x0e, (nvoices - 1) | 0xc0);
}

static void
gus_voice_on(u_int mode)
{
    gus_write8(0x00, (u_char) (mode & 0xfc));
    gus_delay();
    gus_write8(0x00, (u_char) (mode & 0xfc));
}

static void
gus_voice_off(void)
{
    gus_write8(0x00, gus_read8(0x00) | 0x03);
}

static void
gus_voice_mode(u_int m)
{
    u_char   mode = (u_char) (m & 0xff);

    gus_write8(0x00, (gus_read8(0x00) & 0x03) |
	   (mode & 0xfc));	/* Don't touch last two bits */
    gus_delay();
    gus_write8(0x00, (gus_read8(0x00) & 0x03) | (mode & 0xfc));
}

static void
gus_voice_freq(u_long freq)
{
    u_long   divisor = freq_div_table[nr_voices - 14];
    u_short  fc;

    fc = (u_short) (((freq << 9) + (divisor >> 1)) / divisor);
    fc = fc << 1;

    gus_write16(0x01, fc);
}

static void
gus_voice_volume(u_int vol)
{
	gus_write8(0x0d, 0x03);	/* Stop ramp before setting volume */
	gus_write16(0x09, (u_short) (vol << 4));
}

static void
gus_voice_balance(u_int balance)
{
	gus_write8(0x0c, (u_char) (balance & 0xff));
}

static void
gus_ramp_range(u_int low, u_int high)
{
	gus_write8(0x07, (u_char) ((low >> 4) & 0xff));
	gus_write8(0x08, (u_char) ((high >> 4) & 0xff));
}

static void
gus_ramp_rate(u_int scale, u_int rate)
{
	gus_write8(0x06, (u_char) (((scale & 0x03) << 6) | (rate & 0x3f)));
}

static void
gus_rampon(u_int m)
{
	u_char   mode = (u_char) (m & 0xff);

	gus_write8(0x0d, mode & 0xfc);
	gus_delay();
	gus_write8(0x0d, mode & 0xfc);
}

static void
gus_ramp_mode(u_int m)
{
	u_char   mode = (u_char) (m & 0xff);

	gus_write8(0x0d, (gus_read8(0x0d) & 0x03) |
		   (mode & 0xfc));	/* Leave the last 2 bits alone */
	gus_delay();
	gus_write8(0x0d, (gus_read8(0x0d) & 0x03) | (mode & 0xfc));
}

static void
gus_rampoff(void)
{
	gus_write8(0x0d, 0x03);
}

static void
gus_set_voice_pos(int voice, long position)
{
	int             sample_no;

	if ((sample_no = sample_map[voice]) != -1)
		if (position < samples[sample_no].len)
			if (voices[voice].volume_irq_mode == VMODE_START_NOTE)
				voices[voice].offset_pending = position;
			else
				gus_write_addr(0x0a, sample_ptrs[sample_no] + position,
				    samples[sample_no].mode & WAVE_16_BITS);
}

static void
gus_voice_init(int voice)
{
	u_long   flags;

	flags = splhigh();
	gus_select_voice(voice);
	gus_voice_volume(0);
	gus_voice_off();
	gus_write_addr(0x0a, 0, 0);	/* Set current position to 0 */
	gus_write8(0x00, 0x03);	/* Voice off */
	gus_write8(0x0d, 0x03);	/* Ramping off */
	voice_alloc->map[voice] = 0;
	voice_alloc->alloc_times[voice] = 0;
	splx(flags);

}

static void
gus_voice_init2(int voice)
{
	voices[voice].panning = 0;
	voices[voice].mode = 0;
	voices[voice].orig_freq = 20000;
	voices[voice].current_freq = 20000;
	voices[voice].bender = 0;
	voices[voice].bender_range = 200;
	voices[voice].initial_volume = 0;
	voices[voice].current_volume = 0;
	voices[voice].loop_irq_mode = 0;
	voices[voice].loop_irq_parm = 0;
	voices[voice].volume_irq_mode = 0;
	voices[voice].volume_irq_parm = 0;
	voices[voice].env_phase = 0;
	voices[voice].main_vol = 127;
	voices[voice].patch_vol = 127;
	voices[voice].expression_vol = 127;
	voices[voice].sample_pending = -1;
}

static void
step_envelope(int voice)
{
    u_int        vol, prev_vol, phase;
    u_char   rate;
    long int        flags;

    if (voices[voice].mode & WAVE_SUSTAIN_ON && voices[voice].env_phase == 2) {
	flags = splhigh();
	gus_select_voice(voice);
	gus_rampoff();
	splx(flags);
	return;
	/*
	 * Sustain phase begins. Continue envelope after receiving
	 * note off.
	 */
    }
    if (voices[voice].env_phase >= 5) {	/* Envelope finished. Shoot
					 * the voice down */
	gus_voice_init(voice);
	return;
    }
    prev_vol = voices[voice].current_volume;
    phase = ++voices[voice].env_phase;
    compute_volume(voice, voices[voice].midi_volume);
    vol = voices[voice].initial_volume * voices[voice].env_offset[phase] / 255;
    rate = voices[voice].env_rate[phase];

    flags = splhigh();
    gus_select_voice(voice);
    gus_voice_volume(prev_vol);
    gus_write8(0x06, rate);	/* Ramping rate */

    voices[voice].volume_irq_mode = VMODE_ENVELOPE;

    if (((vol - prev_vol) / 64) == 0) {	/* No significant volume
					 * change */
	splx(flags);
	step_envelope(voice);	/* Continue the envelope on the next
				 * step */
	return;
    }
    if (vol > prev_vol) {
	if (vol >= (4096 - 64))
	    vol = 4096 - 65;
	gus_ramp_range(0, vol);
	gus_rampon(0x20);	/* Increasing volume, with IRQ */
    } else {
	if (vol <= 64)
	    vol = 65;
	gus_ramp_range(vol, 4030);
	gus_rampon(0x60);	/* Decreasing volume, with IRQ */
    }
    voices[voice].current_volume = vol;
    splx(flags);
}

static void
init_envelope(int voice)
{
    voices[voice].env_phase = -1;
    voices[voice].current_volume = 64;

    step_envelope(voice);
}

static void
start_release(int voice, long int flags)
{
    if (gus_read8(0x00) & 0x03)
	return;		/* Voice already stopped */

    voices[voice].env_phase = 2;	/* Will be incremented by
				     * step_envelope */

    voices[voice].current_volume =
    voices[voice].initial_volume =
	gus_read16(0x09) >> 4;	/* Get current volume */

    voices[voice].mode &= ~WAVE_SUSTAIN_ON;
    gus_rampoff();
    splx(flags);
    step_envelope(voice);
}

static void
gus_voice_fade(int voice)
{
    int             instr_no = sample_map[voice], is16bits;
    long int        flags;

    flags = splhigh();
    gus_select_voice(voice);

    if (instr_no < 0 || instr_no > MAX_SAMPLE) {
	gus_write8(0x00, 0x03);	/* Hard stop */
	voice_alloc->map[voice] = 0;
	splx(flags);
	return;
    }
    is16bits = (samples[instr_no].mode & WAVE_16_BITS) ? 1 : 0;	/* 8 or 16 bits */

    if (voices[voice].mode & WAVE_ENVELOPES) {
	start_release(voice, flags);
	return;
    }
    /*
     * Ramp the volume down but not too quickly.
     */
    if ((int) (gus_read16(0x09) >> 4) < 100) {	/* Get current volume */
	gus_voice_off();
	gus_rampoff();
	gus_voice_init(voice);
	return;
    }
    gus_ramp_range(65, 4030);
    gus_ramp_rate(2, 4);
    gus_rampon(0x40 | 0x20);/* Down, once, with IRQ */
    voices[voice].volume_irq_mode = VMODE_HALT;
    splx(flags);
}

static void
gus_reset(void)
{
    int             i;

    gus_select_max_voices(24);
    volume_base = 3071;
    volume_scale = 4;
    volume_method = VOL_METHOD_ADAGIO;

    for (i = 0; i < 32; i++) {
	gus_voice_init(i);	/* Turn voice off */
	gus_voice_init2(i);
    }

    inb(u_Status);		/* Touch the status register */

    gus_look8(0x41);	/* Clear any pending DMA IRQs */
    gus_look8(0x49);	/* Clear any pending sample IRQs */

    gus_read8(0x0f);	/* Clear pending IRQs */

}

static void
gus_initialize(void)
{
    u_long   flags;
    u_char   dma_image, irq_image, tmp;

    static u_char gus_irq_map[16] =
	{0, 0, 0, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7};

    static u_char gus_dma_map[8] =
	{0, 1, 0, 2, 0, 3, 4, 5};

    flags = splhigh();
    gus_write8(0x4c, 0);	/* Reset GF1 */
    gus_delay();
    gus_delay();

    gus_write8(0x4c, 1);	/* Release Reset */
    gus_delay();
    gus_delay();

    /*
     * Clear all interrupts
     */

    gus_write8(0x41, 0);	/* DMA control */
    gus_write8(0x45, 0);	/* Timer control */
    gus_write8(0x49, 0);	/* Sample control */

    gus_select_max_voices(24);

    inb(u_Status);		/* Touch the status register */

    gus_look8(0x41);	/* Clear any pending DMA IRQs */
    gus_look8(0x49);	/* Clear any pending sample IRQs */
    gus_read8(0x0f);	/* Clear pending IRQs */

    gus_reset();		/* Resets all voices */

    gus_look8(0x41);	/* Clear any pending DMA IRQs */
    gus_look8(0x49);	/* Clear any pending sample IRQs */
    gus_read8(0x0f);	/* Clear pending IRQs */

    gus_write8(0x4c, 7);	/* Master reset | DAC enable | IRQ enable */

    /*
     * Set up for Digital ASIC
     */

    outb(gus_base + 0x0f, 0x05);

    mix_image |= 0x02;	/* Disable line out (for a moment) */
    outb(u_Mixer, mix_image);

    outb(u_IRQDMAControl, 0x00);

    outb(gus_base + 0x0f, 0x00);

    /*
     * Now set up the DMA and IRQ interface
     * 
     * The GUS supports two IRQs and two DMAs.
     * 
     * Just one DMA channel is used. This prevents simultaneous ADC and DAC.
     * Adding this support requires significant changes to the dmabuf.c,
     * dsp.c and audio.c also.
     */

    irq_image = 0;
    tmp = gus_irq_map[gus_irq];
    if (!tmp)
	printf("Warning! GUS IRQ not selected\n");
    irq_image |= tmp;
    irq_image |= 0x40;	/* Combine IRQ1 (GF1) and IRQ2 (Midi) */

    dual_dma_mode = 1;
    if (gus_dma2 == gus_dma || gus_dma2 == -1) {
	dual_dma_mode = 0;
	dma_image = 0x40;	/* Combine DMA1 (DRAM) and IRQ2 (ADC) */

	tmp = gus_dma_map[gus_dma];
	if (!tmp)
	    printf("Warning! GUS DMA not selected\n");

	dma_image |= tmp;
    } else
	/* Setup dual DMA channel mode for GUS MAX */
    {
	dma_image = gus_dma_map[gus_dma];
	if (!dma_image)
	    printf("Warning! GUS DMA not selected\n");

	tmp = gus_dma_map[gus_dma2] << 3;
	if (!tmp) {
	    printf("Warning! Invalid GUS MAX DMA\n");
	    tmp = 0x40;	/* Combine DMA channels */
	    dual_dma_mode = 0;
	}
	dma_image |= tmp;
    }

    /*
     * For some reason the IRQ and DMA addresses must be written twice
     */

    /*
     * Doing it first time
     */

    outb(u_Mixer, mix_image);	/* Select DMA control */
    outb(u_IRQDMAControl, dma_image | 0x80);	/* Set DMA address */

    outb(u_Mixer, mix_image | 0x40);	/* Select IRQ control */
    outb(u_IRQDMAControl, irq_image);	/* Set IRQ address */

    /*
     * Doing it second time
     */

    outb(u_Mixer, mix_image);	/* Select DMA control */
    outb(u_IRQDMAControl, dma_image);	/* Set DMA address */

    outb(u_Mixer, mix_image | 0x40);	/* Select IRQ control */
    outb(u_IRQDMAControl, irq_image);	/* Set IRQ address */

    gus_select_voice(0);	/* This disables writes to IRQ/DMA reg */

    mix_image &= ~0x02;	/* Enable line out */
    mix_image |= 0x08;	/* Enable IRQ */
    outb(u_Mixer, mix_image);	/* Turn mixer channels on Note! Mic
				 * in is left off. */

    gus_select_voice(0);	/* This disables writes to IRQ/DMA reg */

    gusintr(0);		/* Serve pending interrupts */
    splx(flags);
}

int
gus_wave_detect(int baseaddr)
{
	u_long   i;
	u_long   loc;
	gus_base = baseaddr;

	gus_write8(0x4c, 0);	/* Reset GF1 */
	gus_delay();
	gus_delay();

	gus_write8(0x4c, 1);	/* Release Reset */
	gus_delay();
	gus_delay();

	/* See if there is first block there.... */
	gus_poke(0L, 0xaa);
	if (gus_peek(0L) != 0xaa)
		return (0);

	/* Now zero it out so that I can check for mirroring .. */
	gus_poke(0L, 0x00);
	for (i = 1L; i < 1024L; i++) {
		int             n, failed;

		/* check for mirroring ... */
		if (gus_peek(0L) != 0)
			break;
		loc = i << 10;

		for (n = loc - 1, failed = 0; n <= loc; n++) {
			gus_poke(loc, 0xaa);
			if (gus_peek(loc) != 0xaa)
				failed = 1;

			gus_poke(loc, 0x55);
			if (gus_peek(loc) != 0x55)
				failed = 1;
		}

		if (failed)
			break;
	}
	gus_mem_size = i << 10;
	return 1;
}

static int
guswave_ioctl(int dev,
	      u_int cmd, ioctl_arg arg)
{

    switch (cmd) {
    case SNDCTL_SYNTH_INFO:
	gus_info.nr_voices = nr_voices;
	bcopy(&gus_info, &(((char *) arg)[0]), sizeof(gus_info));
	return 0;
	break;

    case SNDCTL_SEQ_RESETSAMPLES:
	reset_sample_memory();
	return 0;
	break;

    case SNDCTL_SEQ_PERCMODE:
	return 0;
	break;

    case SNDCTL_SYNTH_MEMAVL:
	return gus_mem_size - free_mem_ptr - 32;

    default:
	return -(EINVAL);
    }
}

static int
guswave_set_instr(int dev, int voice, int instr_no)
{
	int             sample_no;

	if (instr_no < 0 || instr_no > MAX_PATCH)
		return -(EINVAL);

	if (voice < 0 || voice > 31)
		return -(EINVAL);

	if (voices[voice].volume_irq_mode == VMODE_START_NOTE) {
		voices[voice].sample_pending = instr_no;
		return 0;
	}
	sample_no = patch_table[instr_no];
	patch_map[voice] = -1;

	if (sample_no < 0) {
		printf("GUS: Undefined patch %d for voice %d\n", instr_no, voice);
		return -(EINVAL);	/* Patch not defined */
	}
	if (sample_ptrs[sample_no] == -1) {	/* Sample not loaded */
		printf("GUS: Sample #%d not loaded for patch %d (voice %d)\n",
		       sample_no, instr_no, voice);
		return -(EINVAL);
	}
	sample_map[voice] = sample_no;
	patch_map[voice] = instr_no;
	return 0;
}

static int
guswave_kill_note(int dev, int voice, int note, int velocity)
{
    u_long   flags;

    flags = splhigh();
    /* voice_alloc->map[voice] = 0xffff; */
    if (voices[voice].volume_irq_mode == VMODE_START_NOTE) {
	voices[voice].kill_pending = 1;
	splx(flags);
    } else {
	splx(flags);
	gus_voice_fade(voice);
    }

    splx(flags);
    return 0;
}

static void
guswave_aftertouch(int dev, int voice, int pressure)
{
}

static void
guswave_panning(int dev, int voice, int value)
{
    if (voice >= 0 || voice < 32)
	voices[voice].panning = value;
}

static void
guswave_volume_method(int dev, int mode)
{
    if (mode == VOL_METHOD_LINEAR || mode == VOL_METHOD_ADAGIO)
	volume_method = mode;
}

static void
compute_volume(int voice, int volume)
{
    if (volume < 128)
	voices[voice].midi_volume = volume;

    switch (volume_method) {
    case VOL_METHOD_ADAGIO:
	voices[voice].initial_volume =
	    gus_adagio_vol(voices[voice].midi_volume, voices[voice].main_vol,
		   voices[voice].expression_vol, voices[voice].patch_vol);
	break;

    case VOL_METHOD_LINEAR:/* Totally ignores patch-volume and expression */
	voices[voice].initial_volume =
	    gus_linear_vol(volume, voices[voice].main_vol);
	break;

    default:
	voices[voice].initial_volume = volume_base +
	    (voices[voice].midi_volume * volume_scale);
    }

    if (voices[voice].initial_volume > 4030)
	voices[voice].initial_volume = 4030;
}

static void
compute_and_set_volume(int voice, int volume, int ramp_time)
{
    int             curr, target, rate;
    u_long   flags;

    compute_volume(voice, volume);
    voices[voice].current_volume = voices[voice].initial_volume;

    flags = splhigh();
    /*
     * CAUTION! Interrupts disabled. Enable them before returning
     */

    gus_select_voice(voice);

    curr = gus_read16(0x09) >> 4;
    target = voices[voice].initial_volume;

    if (ramp_time == INSTANT_RAMP) {
	gus_rampoff();
	gus_voice_volume(target);
	splx(flags);
	return;
    }
    if (ramp_time == FAST_RAMP)
	rate = 63;
    else
	rate = 16;
    gus_ramp_rate(0, rate);

    if ((target - curr) / 64 == 0) {	/* Close enough to target. */
	gus_rampoff();
	gus_voice_volume(target);
	splx(flags);
	return;
    }
    if (target > curr) {
	if (target > (4095 - 65))
	    target = 4095 - 65;
	gus_ramp_range(curr, target);
	gus_rampon(0x00);	/* Ramp up, once, no IRQ */
    } else {
	if (target < 65)
	    target = 65;

	gus_ramp_range(target, curr);
	gus_rampon(0x40);	/* Ramp down, once, no irq */
    }
    splx(flags);
}

static void
dynamic_volume_change(int voice)
{
	u_char   status;
	u_long   flags;

	flags = splhigh();
	gus_select_voice(voice);
	status = gus_read8(0x00);	/* Get voice status */
	splx(flags);

	if (status & 0x03)
		return;		/* Voice was not running */

	if (!(voices[voice].mode & WAVE_ENVELOPES)) {
		compute_and_set_volume(voice, voices[voice].midi_volume, 1);
		return;
	}
	/*
	 * Voice is running and has envelopes.
	 */

	flags = splhigh();
	gus_select_voice(voice);
	status = gus_read8(0x0d);	/* Ramping status */
	splx(flags);

	if (status & 0x03) {	/* Sustain phase? */
		compute_and_set_volume(voice, voices[voice].midi_volume, 1);
		return;
	}
	if (voices[voice].env_phase < 0)
		return;

	compute_volume(voice, voices[voice].midi_volume);

}

static void
guswave_controller(int dev, int voice, int ctrl_num, int value)
{
	u_long   flags;
	u_long   freq;

	if (voice < 0 || voice > 31)
		return;

	switch (ctrl_num) {
	case CTRL_PITCH_BENDER:
		voices[voice].bender = value;

		if (voices[voice].volume_irq_mode != VMODE_START_NOTE) {
			freq = compute_finetune(voices[voice].orig_freq, value,
						voices[voice].bender_range);
			voices[voice].current_freq = freq;

			flags = splhigh();
			gus_select_voice(voice);
			gus_voice_freq(freq);
			splx(flags);
		}
		break;

	case CTRL_PITCH_BENDER_RANGE:
		voices[voice].bender_range = value;
		break;
	case CTL_EXPRESSION:
		value /= 128;
	case CTRL_EXPRESSION:
		if (volume_method == VOL_METHOD_ADAGIO) {
			voices[voice].expression_vol = value;
			if (voices[voice].volume_irq_mode != VMODE_START_NOTE)
				dynamic_volume_change(voice);
		}
		break;

	case CTL_PAN:
		voices[voice].panning = (value * 2) - 128;
		break;

	case CTL_MAIN_VOLUME:
		value = (value * 100) / 16383;

	case CTRL_MAIN_VOLUME:
		voices[voice].main_vol = value;
		if (voices[voice].volume_irq_mode != VMODE_START_NOTE)
			dynamic_volume_change(voice);
		break;

	default:
		break;
	}
}

static int
guswave_start_note2(int dev, int voice, int note_num, int volume)
{
    int             sample, best_sample, best_delta, delta_freq;
    int             is16bits, samplep, patch, pan;
    u_long   note_freq, base_note, freq, flags;
    u_char   mode = 0;

    if (voice < 0 || voice > 31) {
	printf("GUS: Invalid voice\n");
	return -(EINVAL);
    }
    if (note_num == 255) {
	if (voices[voice].mode & WAVE_ENVELOPES) {
	    voices[voice].midi_volume = volume;
	    dynamic_volume_change(voice);
	    return 0;
	}
	compute_and_set_volume(voice, volume, 1);
	return 0;
    }
    if ((patch = patch_map[voice]) == -1)
	return -(EINVAL);
    if ((samplep = patch_table[patch]) == -1)
	return -(EINVAL);
    note_freq = note_to_freq(note_num);

    /*
     * Find a sample within a patch so that the note_freq is between
     * low_note and high_note.
     */
    sample = -1;

    best_sample = samplep;
    best_delta = 1000000;
    while (samplep >= 0 && sample == -1) {
	dbg_samples = samples;
	dbg_samplep = samplep;

	delta_freq = note_freq - samples[samplep].base_note;
	if (delta_freq < 0)
	    delta_freq = -delta_freq;
	if (delta_freq < best_delta) {
	    best_sample = samplep;
	    best_delta = delta_freq;
	}
	if (samples[samplep].low_note <= note_freq &&
		note_freq <= samples[samplep].high_note)
	    sample = samplep;
	else
	    samplep = samples[samplep].key;	/* Follow link */
    }
    if (sample == -1)
	sample = best_sample;

    if (sample == -1) {
	printf("GUS: Patch %d not defined for note %d\n", patch, note_num);
	return 0;	/* Should play default patch ??? */
    }
    is16bits = (samples[sample].mode & WAVE_16_BITS) ? 1 : 0;
    voices[voice].mode = samples[sample].mode;
    voices[voice].patch_vol = samples[sample].volume;

    if (voices[voice].mode & WAVE_ENVELOPES) {
	int             i;

	for (i = 0; i < 6; i++) {
	    voices[voice].env_rate[i] = samples[sample].env_rate[i];
	    voices[voice].env_offset[i] = samples[sample].env_offset[i];
	}
    }
    sample_map[voice] = sample;

    base_note = samples[sample].base_note / 100; /* Try to avoid overflows */
    note_freq /= 100;

    freq = samples[sample].base_freq * note_freq / base_note;

    voices[voice].orig_freq = freq;

    /*
     * Since the pitch bender may have been set before playing the note,
     * we have to calculate the bending now.
     */

    freq = compute_finetune(voices[voice].orig_freq, voices[voice].bender,
			    voices[voice].bender_range);
    voices[voice].current_freq = freq;

    pan = (samples[sample].panning + voices[voice].panning) / 32;
    pan += 7;
    if (pan < 0)
	pan = 0;
    if (pan > 15)
	pan = 15;

    if (samples[sample].mode & WAVE_16_BITS) {
	mode |= 0x04;	/* 16 bits */
	if ((sample_ptrs[sample] >> 18) !=
		((sample_ptrs[sample] + samples[sample].len) >> 18))
	    printf("GUS: Sample address error\n");
    }
    /*
     *    CAUTION!        Interrupts disabled. Don't return before enabling
     */

    flags = splhigh();
    gus_select_voice(voice);
    gus_voice_off();
    gus_rampoff();

    splx(flags);

    if (voices[voice].mode & WAVE_ENVELOPES) {
	compute_volume(voice, volume);
	init_envelope(voice);
    } else {
	compute_and_set_volume(voice, volume, 0);
    }

    flags = splhigh();
    gus_select_voice(voice);

    if (samples[sample].mode & WAVE_LOOP_BACK)
	gus_write_addr(0x0a, sample_ptrs[sample] + samples[sample].len -
		   voices[voice].offset_pending, is16bits);	/* start=end */
    else
	gus_write_addr(0x0a, sample_ptrs[sample] + voices[voice].offset_pending,
		       is16bits);	/* Sample start=begin */

    if (samples[sample].mode & WAVE_LOOPING) {
	mode |= 0x08;

	if (samples[sample].mode & WAVE_BIDIR_LOOP)
	    mode |= 0x10;

	if (samples[sample].mode & WAVE_LOOP_BACK) {
	    gus_write_addr(0x0a,
		    sample_ptrs[sample] + samples[sample].loop_end -
		    voices[voice].offset_pending, is16bits);
	    mode |= 0x40;
	}
	gus_write_addr(0x02, sample_ptrs[sample] + samples[sample].loop_start,
		       is16bits);	/* Loop start location */
	gus_write_addr(0x04, sample_ptrs[sample] + samples[sample].loop_end,
		       is16bits);	/* Loop end location */
    } else {
	mode |= 0x20;	/* Loop IRQ at the end */
	voices[voice].loop_irq_mode = LMODE_FINISH; /* Ramp down at the end */
	voices[voice].loop_irq_parm = 1;
	gus_write_addr(0x02, sample_ptrs[sample],
		       is16bits);	/* Loop start location */
	gus_write_addr(0x04, sample_ptrs[sample] + samples[sample].len - 1,
		       is16bits);	/* Loop end location */
    }
    gus_voice_freq(freq);
    gus_voice_balance(pan);
    gus_voice_on(mode);
    splx(flags);

    return 0;
}

/*
 * New guswave_start_note by Andrew J. Robinson attempts to minimize clicking
 * when the note playing on the voice is changed.  It uses volume ramping.
 */

static int
guswave_start_note(int dev, int voice, int note_num, int volume)
{
    long int        flags;
    int             mode;
    int             ret_val = 0;

    flags = splhigh();
    if (note_num == 255) {
	if (voices[voice].volume_irq_mode == VMODE_START_NOTE) {
	    voices[voice].volume_pending = volume;
	} else {
	    ret_val = guswave_start_note2(dev, voice, note_num, volume);
	}
    } else {
	gus_select_voice(voice);
	mode = gus_read8(0x00);
	if (mode & 0x20)
	    gus_write8(0x00, mode & 0xdf);	/* No interrupt! */

	voices[voice].offset_pending = 0;
	voices[voice].kill_pending = 0;
	voices[voice].volume_irq_mode = 0;
	voices[voice].loop_irq_mode = 0;

	if (voices[voice].sample_pending >= 0) {
	    splx(flags);	/* Run temporarily with interrupts
				 * enabled */
	    guswave_set_instr(voices[voice].dev_pending, voice,
			      voices[voice].sample_pending);
	    voices[voice].sample_pending = -1;
	    flags = splhigh();
	    gus_select_voice(voice);	/* Reselect the voice
					 * (just to be sure) */
	}
	if ((mode & 0x01) || (int) ((gus_read16(0x09) >> 4) < 2065)) {
	    ret_val = guswave_start_note2(dev, voice, note_num, volume);
	} else {
	    voices[voice].dev_pending = dev;
	    voices[voice].note_pending = note_num;
	    voices[voice].volume_pending = volume;
	    voices[voice].volume_irq_mode = VMODE_START_NOTE;

	    gus_rampoff();
	    gus_ramp_range(2000, 4065);
	    gus_ramp_rate(0, 63);	/* Fastest possible rate */
	    gus_rampon(0x20 | 0x40);	/* Ramp down, once, irq */
	}
    }
    splx(flags);
    return ret_val;
}

static void
guswave_reset(int dev)
{
    int             i;

    for (i = 0; i < 32; i++) {
	gus_voice_init(i);
	gus_voice_init2(i);
    }
}

static int
guswave_open(int dev, int mode)
{
    int             err;
    int             otherside = audio_devs[dev]->otherside;

    if (otherside != -1) {
	if (audio_devs[otherside]->busy)
	    return -(EBUSY);
    }
    if (audio_devs[dev]->busy)
	return -(EBUSY);

    gus_initialize();
    voice_alloc->timestamp = 0;

    if ((err = DMAbuf_open_dma(gus_devnum)) < 0) {
	printf("GUS: Loading saples without DMA\n");
	gus_no_dma = 1;	/* Upload samples using PIO */
    } else
	gus_no_dma = 0;

    dram_sleep_flag.aborting = 0;
    dram_sleep_flag.mode = WK_NONE;
    active_device = GUS_DEV_WAVE;

    audio_devs[dev]->busy = 1;
    gus_reset();

    return 0;
}

static void
guswave_close(int dev)
{
    int             otherside = audio_devs[dev]->otherside;

    if (otherside != -1) {
	if (audio_devs[otherside]->busy)
	    return;
    }
    audio_devs[dev]->busy = 0;

    active_device = 0;
    gus_reset();

    if (!gus_no_dma)
	DMAbuf_close_dma(gus_devnum);
}

static int
guswave_load_patch(int dev, int format, snd_rw_buf * addr,
		   int offs, int count, int pmgr_flag)
{
    struct patch_info patch;
    int             instr;
    long            sizeof_patch;

    u_long   blk_size, blk_end, left, src_offs, target;

    sizeof_patch = offsetof(struct patch_info, data); /* Header size */

    if (format != GUS_PATCH) {
	printf("GUS Error: Invalid patch format (key) 0x%x\n", format);
	return -(EINVAL);
    }
    if (count < sizeof_patch) {
	printf("GUS Error: Patch header too short\n");
	return -(EINVAL);
    }
    count -= sizeof_patch;

    if (free_sample >= MAX_SAMPLE) {
	printf("GUS: Sample table full\n");
	return -(ENOSPC);
    }
    /*
     * Copy the header from user space but ignore the first bytes which
     * have been transferred already.
     */

    if (uiomove(&((char *) &patch)[offs], sizeof_patch - offs, addr)) {
	printf("audio: Bad copyin()!\n");
    };

    instr = patch.instr_no;

    if (instr < 0 || instr > MAX_PATCH) {
	printf("GUS: Invalid patch number %d\n", instr);
	return -(EINVAL);
    }
    if (count < patch.len) {
	printf("GUS Warning: Patch record too short (%d<%d)\n",
	       count, (int) patch.len);
	patch.len = count;
    }
    if (patch.len <= 0 || patch.len > gus_mem_size) {
	printf("GUS: Invalid sample length %d\n", (int) patch.len);
	return -(EINVAL);
    }
    if (patch.mode & WAVE_LOOPING) {
	if (patch.loop_start < 0 || patch.loop_start >= patch.len) {
	    printf("GUS: Invalid loop start\n");
	    return -(EINVAL);
	}
	if (patch.loop_end < patch.loop_start || patch.loop_end > patch.len) {
	    printf("GUS: Invalid loop end\n");
	    return -(EINVAL);
	}
    }
    free_mem_ptr = (free_mem_ptr + 31) & ~31;	/* 32 byte alignment */

#define GUS_BANK_SIZE (256*1024)

    if (patch.mode & WAVE_16_BITS) {
	/*
	 * 16 bit samples must fit one 256k bank.
	 */
	if (patch.len >= GUS_BANK_SIZE) {
	    printf("GUS: Sample (16 bit) too long %d\n", (int) patch.len);
	    return -(ENOSPC);
	}
	if ((free_mem_ptr / GUS_BANK_SIZE) !=
		((free_mem_ptr + patch.len) / GUS_BANK_SIZE)) {
	    u_long   tmp_mem =	/* Aligning to 256K */
		    ((free_mem_ptr / GUS_BANK_SIZE) + 1) * GUS_BANK_SIZE;

	    if ((tmp_mem + patch.len) > gus_mem_size)
		return -(ENOSPC);

	    free_mem_ptr = tmp_mem;	/* This leaves unusable memory */
	}
    }
    if ((free_mem_ptr + patch.len) > gus_mem_size)
	return -(ENOSPC);

    sample_ptrs[free_sample] = free_mem_ptr;

    /*
     * Tremolo is not possible with envelopes
     */

    if (patch.mode & WAVE_ENVELOPES)
	patch.mode &= ~WAVE_TREMOLO;

    bcopy(&patch, (char *) &samples[free_sample], sizeof_patch);

    /*
     * Link this_one sample to the list of samples for patch 'instr'.
     */

    samples[free_sample].key = patch_table[instr];
    patch_table[instr] = free_sample;

    /*
     * Use DMA to transfer the wave data to the DRAM
     */

    left = patch.len;
    src_offs = 0;
    target = free_mem_ptr;

    while (left) {		/* Not completely transferred yet */
	/* blk_size = audio_devs[gus_devnum]->buffsize; */
	blk_size = audio_devs[gus_devnum]->dmap_out->bytes_in_use;
	if (blk_size > left)
	    blk_size = left;

	/*
	 * DMA cannot cross 256k bank boundaries. Check for that.
	 */
	blk_end = target + blk_size;

	if ((target >> 18) != (blk_end >> 18)) {	/* Split the block */
	    blk_end &= ~(256 * 1024 - 1);
	    blk_size = blk_end - target;
	}
	if (gus_no_dma) {
	    /*
	     * For some reason the DMA is not possible. We have
	     * to use PIO.
	     */
	    long            i;
	    u_char   data;

	    for (i = 0; i < blk_size; i++) {
		uiomove((char *) &(data), 1, addr);
		if (patch.mode & WAVE_UNSIGNED)
		    if (!(patch.mode & WAVE_16_BITS) || (i & 0x01))
			data ^= 0x80;	/* Convert to signed */
		gus_poke(target + i, data);
	    }
	} else {
	    u_long   address, hold_address;
	    u_char   dma_command;
	    u_long   flags;

	    /*
	     * OK, move now. First in and then out.
	     */

	    if (uiomove(audio_devs[gus_devnum]->dmap_out->raw_buf, blk_size, addr)) {
		printf("audio: Bad copyin()!\n");
	    };

	    flags = splhigh();
	    /******** INTERRUPTS DISABLED NOW ********/
	    gus_write8(0x41, 0);	/* Disable GF1 DMA */
	    DMAbuf_start_dma(gus_devnum,
			 audio_devs[gus_devnum]->dmap_out->raw_buf_phys,
			 blk_size, 1);

	    /*
	     * Set the DRAM address for the wave data
	     */

	    address = target;

	    if (audio_devs[gus_devnum]->dmachan1 > 3) {
		hold_address = address;
		address = address >> 1;
		address &= 0x0001ffffL;
		address |= (hold_address & 0x000c0000L);
	    }
	    gus_write16(0x42, (address >> 4) & 0xffff);	/* DRAM DMA address */

	    /*
	     * Start the DMA transfer
	     */

	    dma_command = 0x21;	/* IRQ enable, DMA start */
	    if (patch.mode & WAVE_UNSIGNED)
		dma_command |= 0x80;	/* Invert MSB */
	    if (patch.mode & WAVE_16_BITS)
		dma_command |= 0x40;	/* 16 bit _DATA_ */
	    if (audio_devs[gus_devnum]->dmachan1 > 3)
		dma_command |= 0x04;	/* 16 bit DMA _channel_ */

	    gus_write8(0x41, dma_command);	/* Lets bo luteet (=bugs) */

	    /*
	     * Sleep here until the DRAM DMA done interrupt is
	     * served
	     */
	    active_device = GUS_DEV_WAVE;


	    {
		int   chn;

		dram_sleep_flag.mode = WK_SLEEP;
		dram_sleeper = &chn;
		DO_SLEEP(chn, dram_sleep_flag, hz);

	    };
	    if ((dram_sleep_flag.mode & WK_TIMEOUT))
		printf("GUS: DMA Transfer timed out\n");
	    splx(flags);
	}

	/*
	 * Now the next part
	 */

	left -= blk_size;
	src_offs += blk_size;
	target += blk_size;

	gus_write8(0x41, 0);	/* Stop DMA */
    }

    free_mem_ptr += patch.len;

    if (!pmgr_flag)
	pmgr_inform(dev, PM_E_PATCH_LOADED, instr, free_sample, 0, 0);
    free_sample++;
    return 0;
}

static void
guswave_hw_control(int dev, u_char *event)
{
	int             voice, cmd;
	u_short  p1, p2;
	u_long   plong, flags;

	cmd = event[2];
	voice = event[3];
	p1 = *(u_short *) &event[4];
	p2 = *(u_short *) &event[6];
	plong = *(u_long *) &event[4];

	if ((voices[voice].volume_irq_mode == VMODE_START_NOTE) &&
	    (cmd != _GUS_VOICESAMPLE) && (cmd != _GUS_VOICE_POS))
		do_volume_irq(voice);

	switch (cmd) {

	case _GUS_NUMVOICES:
		flags = splhigh();
		gus_select_voice(voice);
		gus_select_max_voices(p1);
		splx(flags);
		break;

	case _GUS_VOICESAMPLE:
		guswave_set_instr(dev, voice, p1);
		break;

	case _GUS_VOICEON:
		flags = splhigh();
		gus_select_voice(voice);
		p1 &= ~0x20;	/* Don't allow interrupts */
		gus_voice_on(p1);
		splx(flags);
		break;

	case _GUS_VOICEOFF:
		flags = splhigh();
		gus_select_voice(voice);
		gus_voice_off();
		splx(flags);
		break;

	case _GUS_VOICEFADE:
		gus_voice_fade(voice);
		break;

	case _GUS_VOICEMODE:
		flags = splhigh();
		gus_select_voice(voice);
		p1 &= ~0x20;	/* Don't allow interrupts */
		gus_voice_mode(p1);
		splx(flags);
		break;

	case _GUS_VOICEBALA:
		flags = splhigh();
		gus_select_voice(voice);
		gus_voice_balance(p1);
		splx(flags);
		break;

	case _GUS_VOICEFREQ:
		flags = splhigh();
		gus_select_voice(voice);
		gus_voice_freq(plong);
		splx(flags);
		break;

	case _GUS_VOICEVOL:
		flags = splhigh();
		gus_select_voice(voice);
		gus_voice_volume(p1);
		splx(flags);
		break;

	case _GUS_VOICEVOL2:	/* Just update the software voice level */
		voices[voice].initial_volume =
			voices[voice].current_volume = p1;
		break;

	case _GUS_RAMPRANGE:
		if (voices[voice].mode & WAVE_ENVELOPES)
			break;	/* NO-NO */
		flags = splhigh();
		gus_select_voice(voice);
		gus_ramp_range(p1, p2);
		splx(flags);
		break;

	case _GUS_RAMPRATE:
		if (voices[voice].mode & WAVE_ENVELOPES)
			break;	/* NJET-NJET */
		flags = splhigh();
		gus_select_voice(voice);
		gus_ramp_rate(p1, p2);
		splx(flags);
		break;

	case _GUS_RAMPMODE:
		if (voices[voice].mode & WAVE_ENVELOPES)
			break;	/* NO-NO */
		flags = splhigh();
		gus_select_voice(voice);
		p1 &= ~0x20;	/* Don't allow interrupts */
		gus_ramp_mode(p1);
		splx(flags);
		break;

	case _GUS_RAMPON:
		if (voices[voice].mode & WAVE_ENVELOPES)
			break;	/* EI-EI */
		flags = splhigh();
		gus_select_voice(voice);
		p1 &= ~0x20;	/* Don't allow interrupts */
		gus_rampon(p1);
		splx(flags);
		break;

	case _GUS_RAMPOFF:
		if (voices[voice].mode & WAVE_ENVELOPES)
			break;	/* NEJ-NEJ */
		flags = splhigh();
		gus_select_voice(voice);
		gus_rampoff();
		splx(flags);
		break;

	case _GUS_VOLUME_SCALE:
		volume_base = p1;
		volume_scale = p2;
		break;

	case _GUS_VOICE_POS:
		flags = splhigh();
		gus_select_voice(voice);
		gus_set_voice_pos(voice, plong);
		splx(flags);
		break;

	default:;
	}
}

static int
gus_sampling_set_speed(int speed)
{

	if (speed <= 0)
		speed = gus_sampling_speed;

	RANGE(speed, 4000, 44100);
	gus_sampling_speed = speed;

	if (only_read_access) {
		/* Compute nearest valid recording speed  and return it */

		speed = (9878400 / (gus_sampling_speed + 2)) / 16;
		speed = (9878400 / (speed * 16)) - 2;
	}
	return speed;
}

static int
gus_sampling_set_channels(int channels)
{
	if (!channels)
		return gus_sampling_channels;
	RANGE(channels, 1, 2);
	gus_sampling_channels = channels;
	return channels;
}

static int
gus_sampling_set_bits(int bits)
{
	if (!bits)
		return gus_sampling_bits;

	if (bits != 8 && bits != 16)
		bits = 8;

	if (only_8_bits)
		bits = 8;

	gus_sampling_bits = bits;
	return bits;
}

static int
gus_sampling_ioctl(int dev, u_int cmd, ioctl_arg arg, int local)
{
	switch (cmd) {
		case SOUND_PCM_WRITE_RATE:
		if (local)
			return gus_sampling_set_speed((int) arg);
		return *(int *) arg = gus_sampling_set_speed((*(int *) arg));
		break;

	case SOUND_PCM_READ_RATE:
		if (local)
			return gus_sampling_speed;
		return *(int *) arg = gus_sampling_speed;
		break;

	case SNDCTL_DSP_STEREO:
		if (local)
			return gus_sampling_set_channels((int) arg + 1) - 1;
		return *(int *) arg = gus_sampling_set_channels((*(int *) arg) + 1) - 1;
		break;

	case SOUND_PCM_WRITE_CHANNELS:
		if (local)
			return gus_sampling_set_channels((int) arg);
		return *(int *) arg = gus_sampling_set_channels((*(int *) arg));
		break;

	case SOUND_PCM_READ_CHANNELS:
		if (local)
			return gus_sampling_channels;
		return *(int *) arg = gus_sampling_channels;
		break;

	case SNDCTL_DSP_SETFMT:
		if (local)
			return gus_sampling_set_bits((int) arg);
		return *(int *) arg = gus_sampling_set_bits((*(int *) arg));
		break;

	case SOUND_PCM_READ_BITS:
		if (local)
			return gus_sampling_bits;
		return *(int *) arg = gus_sampling_bits;

	case SOUND_PCM_WRITE_FILTER:	/* NOT POSSIBLE */
		return *(int *) arg = -(EINVAL);
		break;

	case SOUND_PCM_READ_FILTER:
		return *(int *) arg = -(EINVAL);
		break;

	}
	return -(EINVAL);
}

static void
gus_sampling_reset(int dev)
{
	if (recording_active) {
		gus_write8(0x49, 0x00);	/* Halt recording */
		set_input_volumes();
	}
}

static int
gus_sampling_open(int dev, int mode)
{

    int             otherside = audio_devs[dev]->otherside;
    if (otherside != -1) {
	if (audio_devs[otherside]->busy)
	    return -(EBUSY);
    }
    if (audio_devs[dev]->busy)
	return -(EBUSY);


    gus_initialize();

    active_device = 0;

    gus_reset();
    reset_sample_memory();
    gus_select_max_voices(14);

    pcm_active = 0;
    dma_active = 0;
    pcm_opened = 1;
    audio_devs[dev]->busy = 1;

    if (mode & OPEN_READ) {
	recording_active = 1;
	set_input_volumes();
    }
    only_read_access = !(mode & OPEN_WRITE);
    only_8_bits = mode & OPEN_READ;
    if (only_8_bits)
	audio_devs[dev]->format_mask = AFMT_U8;
    else
	audio_devs[dev]->format_mask = AFMT_U8 | AFMT_S16_LE;

    return 0;
}

static void
gus_sampling_close(int dev)
{
	int             otherside = audio_devs[dev]->otherside;
	audio_devs[dev]->busy = 0;

	if (otherside != -1) {
		if (audio_devs[otherside]->busy)
			return;
	}
	gus_reset();

	pcm_opened = 0;
	active_device = 0;

	if (recording_active) {
		gus_write8(0x49, 0x00);	/* Halt recording */
		set_input_volumes();
	}
	recording_active = 0;
}

static void
gus_sampling_update_volume(void)
{
    u_long   flags;
    int             voice;

    if (pcm_active && pcm_opened)
	for (voice = 0; voice < gus_sampling_channels; voice++) {
	    flags = splhigh();
	    gus_select_voice(voice);
	    gus_rampoff();
	    gus_voice_volume(1530 + (25 * gus_pcm_volume));
	    gus_ramp_range(65, 1530 + (25 * gus_pcm_volume));
	    splx(flags);
	}
}

static void
play_next_pcm_block(void)
{
    u_long   flags;
    int             speed = gus_sampling_speed;
    int             this_one, is16bits, chn;
    u_long   dram_loc;
    u_char   mode[2], ramp_mode[2];

    if (!pcm_qlen)
	return;

    this_one = pcm_head;

    for (chn = 0; chn < gus_sampling_channels; chn++) {
	mode[chn] = 0x00;
	ramp_mode[chn] = 0x03;	/* Ramping and rollover off */

	if (chn == 0) {
	    mode[chn] |= 0x20;	/* Loop IRQ */
	    voices[chn].loop_irq_mode = LMODE_PCM;
	}
	if (gus_sampling_bits != 8) {
	    is16bits = 1;
	    mode[chn] |= 0x04;	/* 16 bit data */
	} else
	    is16bits = 0;

	dram_loc = this_one * pcm_bsize;
	dram_loc += chn * pcm_banksize;

	if (this_one == (pcm_nblk - 1)) {	/* Last fragment of the
						 * DRAM buffer */
	    mode[chn] |= 0x08;	/* Enable loop */
	    ramp_mode[chn] = 0x03;	/* Disable rollover bit */
	} else {
	    if (chn == 0)
		ramp_mode[chn] = 0x04;	/* Enable rollover bit */
	}

	flags = splhigh();
	gus_select_voice(chn);
	gus_voice_freq(speed);

	if (gus_sampling_channels == 1)
	    gus_voice_balance(7);	/* mono */
	else if (chn == 0)
	    gus_voice_balance(0);	/* left */
	else
	    gus_voice_balance(15);	/* right */

	if (!pcm_active) {	/* Playback not already active */
	    /*
	     * The playback was not started yet (or there has
	     * been a pause). Start the voice (again) and ask for
	     * a rollover irq at the end of this_one block. If
	     * this_one one is last of the buffers, use just the
	     * normal loop with irq.
	     */

	    gus_voice_off();
	    gus_rampoff();
	    gus_voice_volume(1530 + (25 * gus_pcm_volume));
	    gus_ramp_range(65, 1530 + (25 * gus_pcm_volume));

	    gus_write_addr(0x0a, dram_loc, is16bits);	/* Starting position */
	    gus_write_addr(0x02, chn * pcm_banksize, is16bits);	/* Loop start */

	    if (chn != 0)
		gus_write_addr(0x04, pcm_banksize + (pcm_bsize * pcm_nblk) - 1,
			   is16bits);	/* Loop end location */
	}
	if (chn == 0)
	    gus_write_addr(0x04, dram_loc + pcm_datasize[this_one] - 1,
			       is16bits);	/* Loop end location */
	else
	    mode[chn] |= 0x08;	/* Enable looping */

	if (pcm_datasize[this_one] != pcm_bsize) {
	    /*
	     * Incompletely filled block. Possibly the last one.
	     */
	    if (chn == 0) {
		mode[chn] &= ~0x08;	/* Disable looping */
		mode[chn] |= 0x20;	/* Enable IRQ at the end */
		voices[0].loop_irq_mode = LMODE_PCM_STOP;
		ramp_mode[chn] = 0x03;	/* No rollover bit */
	    } else {
		gus_write_addr(0x04, dram_loc + pcm_datasize[this_one],
			       is16bits);	/* Loop end location */
		mode[chn] &= ~0x08;	/* Disable looping */
	    }
	}
	splx(flags);
    }

    for (chn = 0; chn < gus_sampling_channels; chn++) {
	flags = splhigh();
	gus_select_voice(chn);
	gus_write8(0x0d, ramp_mode[chn]);
	gus_voice_on(mode[chn]);
	splx(flags);
    }

    pcm_active = 1;
}

static void
gus_transfer_output_block(int dev, u_long buf,
			  int total_count, int intrflag, int chn)
{
	/*
	 * This routine transfers one block of audio data to the DRAM. In
	 * mono mode it's called just once. When in stereo mode, this_one
	 * routine is called once for both channels.
	 * 
	 * The left/mono channel data is transferred to the beginning of dram
	 * and the right data to the area pointed by gus_page_size.
	 */

	int             this_one, count;
	u_long   flags;
	u_char   dma_command;
	u_long   address, hold_address;

	flags = splhigh();

	count = total_count / gus_sampling_channels;

	if (chn == 0) {
		if (pcm_qlen >= pcm_nblk)
			printf("GUS Warning: PCM buffers out of sync\n");

		this_one = pcm_current_block = pcm_tail;
		pcm_qlen++;
		pcm_tail = (pcm_tail + 1) % pcm_nblk;
		pcm_datasize[this_one] = count;
	} else
		this_one = pcm_current_block;

	gus_write8(0x41, 0);	/* Disable GF1 DMA */
	DMAbuf_start_dma(dev, buf + (chn * count), count, 1);

	address = this_one * pcm_bsize;
	address += chn * pcm_banksize;

	if (audio_devs[dev]->dmachan1 > 3) {
		hold_address = address;
		address = address >> 1;
		address &= 0x0001ffffL;
		address |= (hold_address & 0x000c0000L);
	}
	gus_write16(0x42, (address >> 4) & 0xffff);	/* DRAM DMA address */

	dma_command = 0x21;	/* IRQ enable, DMA start */

	if (gus_sampling_bits != 8)
		dma_command |= 0x40;	/* 16 bit _DATA_ */
	else
		dma_command |= 0x80;	/* Invert MSB */

	if (audio_devs[dev]->dmachan1 > 3)
		dma_command |= 0x04;	/* 16 bit DMA channel */

	gus_write8(0x41, dma_command);	/* Kickstart */

	if (chn == (gus_sampling_channels - 1)) {	/* Last channel */
		/*
		 * Last (right or mono) channel data
		 */
		dma_active = 1;	/* DMA started. There is a unacknowledged
				 * buffer */
		active_device = GUS_DEV_PCM_DONE;
		if (!pcm_active && (pcm_qlen > 0 || count < pcm_bsize)) {
			play_next_pcm_block();
		}
	} else {
		/*
		 * Left channel data. The right channel is transferred after
		 * DMA interrupt
		 */
		active_device = GUS_DEV_PCM_CONTINUE;
	}

	splx(flags);
}

static void
gus_sampling_output_block(int dev, u_long buf, int total_count,
			  int intrflag, int restart_dma)
{
	pcm_current_buf = buf;
	pcm_current_count = total_count;
	pcm_current_intrflag = intrflag;
	pcm_current_dev = dev;
	gus_transfer_output_block(dev, buf, total_count, intrflag, 0);
}

static void
gus_sampling_start_input(int dev, u_long buf, int count,
			 int intrflag, int restart_dma)
{
	u_long   flags;
	u_char   mode;

	flags = splhigh();

	DMAbuf_start_dma(dev, buf, count, 0);

	mode = 0xa0;		/* DMA IRQ enabled, invert MSB */

	if (audio_devs[dev]->dmachan2 > 3)
		mode |= 0x04;	/* 16 bit DMA channel */
	if (gus_sampling_channels > 1)
		mode |= 0x02;	/* Stereo */
	mode |= 0x01;		/* DMA enable */

	gus_write8(0x49, mode);

	splx(flags);
}

static int
gus_sampling_prepare_for_input(int dev, int bsize, int bcount)
{
    u_int    rate;

    rate = (9878400 / (gus_sampling_speed + 2)) / 16;

    gus_write8(0x48, rate & 0xff);	/* Set sampling rate */

    if (gus_sampling_bits != 8) {
	printf("GUS Error: 16 bit recording not supported\n");
	return -(EINVAL);
    }
    return 0;
}

static int
gus_sampling_prepare_for_output(int dev, int bsize, int bcount)
{
	int             i;

	long            mem_ptr, mem_size;

	mem_ptr = 0;
	mem_size = gus_mem_size / gus_sampling_channels;

	if (mem_size > (256 * 1024))
		mem_size = 256 * 1024;

	pcm_bsize = bsize / gus_sampling_channels;
	pcm_head = pcm_tail = pcm_qlen = 0;

	pcm_nblk = MAX_PCM_BUFFERS;
	if ((pcm_bsize * pcm_nblk) > mem_size)
		pcm_nblk = mem_size / pcm_bsize;

	for (i = 0; i < pcm_nblk; i++)
		pcm_datasize[i] = 0;

	pcm_banksize = pcm_nblk * pcm_bsize;

	if (gus_sampling_bits != 8 && pcm_banksize == (256 * 1024))
		pcm_nblk--;

	return 0;
}

static int
gus_local_qlen(int dev)
{
	return pcm_qlen;
}

static void
gus_copy_from_user(int dev, char *localbuf, int localoffs,
		   snd_rw_buf * userbuf, int useroffs, int len)
{
	if (gus_sampling_channels == 1) {

		if (uiomove(&localbuf[localoffs], len, userbuf)) {
			printf("audio: Bad copyin()!\n");
		};
	} else if (gus_sampling_bits == 8) {
		int             in_left = useroffs;
		int             in_right = useroffs + 1;
		char           *out_left, *out_right;
		int             i;

		len /= 2;
		localoffs /= 2;
		out_left = &localbuf[localoffs];
		out_right = out_left + pcm_bsize;

		for (i = 0; i < len; i++) {
			uiomove((char *) &(*out_left++), 1, userbuf);
			in_left += 2;
			uiomove((char *) &(*out_right++), 1, userbuf);
			in_right += 2;
		}
	} else {
		int             in_left = useroffs;
		int             in_right = useroffs + 2;
		short          *out_left, *out_right;
		int             i;

		len /= 4;
		localoffs /= 2;

		out_left = (short *) &localbuf[localoffs];
		out_right = out_left + (pcm_bsize / 2);

		for (i = 0; i < len; i++) {
			uiomove((char *) &(*out_left++), 2, userbuf);
			in_left += 2;
			uiomove((char *) &(*out_right++), 2, userbuf);
			in_right += 2;
		}
	}
}

static struct audio_operations gus_sampling_operations =
{
	"Gravis UltraSound",
	NEEDS_RESTART,
	AFMT_U8 | AFMT_S16_LE,
	NULL,
	gus_sampling_open,
	gus_sampling_close,
	gus_sampling_output_block,
	gus_sampling_start_input,
	gus_sampling_ioctl,
	gus_sampling_prepare_for_input,
	gus_sampling_prepare_for_output,
	gus_sampling_reset,
	gus_sampling_reset,
	gus_local_qlen,
	gus_copy_from_user
};

static void
guswave_setup_voice(int dev, int voice, int chn)
{
	struct channel_info *info =
	&synth_devs[dev]->chn_info[chn];

	guswave_set_instr(dev, voice, info->pgm_num);

	voices[voice].expression_vol =
		info->controllers[CTL_EXPRESSION];	/* Just msb */
	voices[voice].main_vol =
		(info->controllers[CTL_MAIN_VOLUME] * 100) / 128;
	voices[voice].panning =
		(info->controllers[CTL_PAN] * 2) - 128;
	voices[voice].bender = info->bender_value;
}

static void
guswave_bender(int dev, int voice, int value)
{
	int             freq;
	u_long   flags;

	voices[voice].bender = value - 8192;
	freq = compute_finetune(voices[voice].orig_freq, value - 8192,
				voices[voice].bender_range);
	voices[voice].current_freq = freq;

	flags = splhigh();
	gus_select_voice(voice);
	gus_voice_freq(freq);
	splx(flags);
}

static int
guswave_patchmgr(int dev, struct patmgr_info * rec)
{
	int             i, n;

	switch (rec->command) {
	case PM_GET_DEVTYPE:
		rec->parm1 = PMTYPE_WAVE;
		return 0;
		break;

	case PM_GET_NRPGM:
		rec->parm1 = MAX_PATCH;
		return 0;
		break;

	case PM_GET_PGMMAP:
		rec->parm1 = MAX_PATCH;

		for (i = 0; i < MAX_PATCH; i++) {
			int             ptr = patch_table[i];

			rec->data.data8[i] = 0;

			while (ptr >= 0 && ptr < free_sample) {
				rec->data.data8[i]++;
				ptr = samples[ptr].key;	/* Follow link */
			}
		}
		return 0;
		break;

	case PM_GET_PGM_PATCHES:
		{
			int             ptr = patch_table[rec->parm1];

			n = 0;

			while (ptr >= 0 && ptr < free_sample) {
				rec->data.data32[n++] = ptr;
				ptr = samples[ptr].key;	/* Follow link */
			}
		}
		rec->parm1 = n;
		return 0;
		break;

	case PM_GET_PATCH:
		{
			int             ptr = rec->parm1;
			struct patch_info *pat;

			if (ptr < 0 || ptr >= free_sample)
				return -(EINVAL);

			bcopy((char *) &samples[ptr], rec->data.data8, sizeof(struct patch_info));

			pat = (struct patch_info *) rec->data.data8;

			pat->key = GUS_PATCH;	/* Restore patch type */
			rec->parm1 = sample_ptrs[ptr];	/* DRAM location */
			rec->parm2 = sizeof(struct patch_info);
		}
		return 0;
		break;

	case PM_SET_PATCH:
		{
			int             ptr = rec->parm1;
			struct patch_info *pat;

			if (ptr < 0 || ptr >= free_sample)
				return -(EINVAL);

			pat = (struct patch_info *) rec->data.data8;

			if (pat->len > samples[ptr].len)	/* Cannot expand sample */
				return -(EINVAL);

			pat->key = samples[ptr].key;	/* Ensure the link is
							 * correct */

			bcopy(rec->data.data8, (char *) &samples[ptr], sizeof(struct patch_info));

			pat->key = GUS_PATCH;
		}
		return 0;
		break;

	case PM_READ_PATCH:	/* Returns a block of wave data from the DRAM */
		{
			int             sample = rec->parm1;
			int             n;
			long            offs = rec->parm2;
			int             l = rec->parm3;

			if (sample < 0 || sample >= free_sample)
				return -(EINVAL);

			if (offs < 0 || offs >= samples[sample].len)
				return -(EINVAL);	/* Invalid offset */

			n = samples[sample].len - offs;	/* Num of bytes left */

			if (l > n)
				l = n;

			if (l > sizeof(rec->data.data8))
				l = sizeof(rec->data.data8);

			if (l <= 0)
				return -(EINVAL);	/* Was there a bug? */

			offs += sample_ptrs[sample];	/* Begin offsess +
							 * offset to DRAM */

			for (n = 0; n < l; n++)
				rec->data.data8[n] = gus_peek(offs++);
			rec->parm1 = n;	/* Nr of bytes copied */
		}
		return 0;
		break;

	case PM_WRITE_PATCH:	/* Writes a block of wave data to the DRAM */
		{
			int             sample = rec->parm1;
			int             n;
			long            offs = rec->parm2;
			int             l = rec->parm3;

			if (sample < 0 || sample >= free_sample)
				return -(EINVAL);

			if (offs < 0 || offs >= samples[sample].len)
				return -(EINVAL);	/* Invalid offset */

			n = samples[sample].len - offs;	/* Nr of bytes left */

			if (l > n)
				l = n;

			if (l > sizeof(rec->data.data8))
				l = sizeof(rec->data.data8);

			if (l <= 0)
				return -(EINVAL);	/* Was there a bug? */

			offs += sample_ptrs[sample];	/* Begin offsess +
							 * offset to DRAM */

			for (n = 0; n < l; n++)
				gus_poke(offs++, rec->data.data8[n]);
			rec->parm1 = n;	/* Nr of bytes copied */
		}
		return 0;
		break;

	default:
		return -(EINVAL);
	}
}

static int
guswave_alloc(int dev, int chn, int note, struct voice_alloc_info * alloc)
{
	int             i, p, best = -1, best_time = 0x7fffffff;

	p = alloc->ptr;
	/*
	 * First look for a completely stopped voice
	 */

	for (i = 0; i < alloc->max_voice; i++) {
		if (alloc->map[p] == 0) {
			alloc->ptr = p;
			return p;
		}
		if (alloc->alloc_times[p] < best_time) {
			best = p;
			best_time = alloc->alloc_times[p];
		}
		p = (p + 1) % alloc->max_voice;
	}

	/*
	 * Then look for a releasing voice
	 */

	for (i = 0; i < alloc->max_voice; i++) {
		if (alloc->map[p] == 0xffff) {
			alloc->ptr = p;
			return p;
		}
		p = (p + 1) % alloc->max_voice;
	}

	if (best >= 0)
		p = best;

	alloc->ptr = p;
	return p;
}

static struct synth_operations guswave_operations =
{
	&gus_info,
	0,
	SYNTH_TYPE_SAMPLE,
	SAMPLE_TYPE_GUS,
	guswave_open,
	guswave_close,
	guswave_ioctl,
	guswave_kill_note,
	guswave_start_note,
	guswave_set_instr,
	guswave_reset,
	guswave_hw_control,
	guswave_load_patch,
	guswave_aftertouch,
	guswave_controller,
	guswave_panning,
	guswave_volume_method,
	guswave_patchmgr,
	guswave_bender,
	guswave_alloc,
	guswave_setup_voice
};

static void
set_input_volumes(void)
{
	u_long   flags;
	u_char   mask = 0xff & ~0x06;	/* Just line out enabled */

	if (have_gus_max)	/* Don't disturb GUS MAX */
		return;

	flags = splhigh();

	/*
	 * Enable channels having vol > 10% Note! bit 0x01 means the line in
	 * DISABLED while 0x04 means the mic in ENABLED.
	 */
	if (gus_line_vol > 10)
		mask &= ~0x01;
	if (gus_mic_vol > 10)
		mask |= 0x04;

	if (recording_active) {
		/*
		 * Disable channel, if not selected for recording
		 */
		if (!(gus_recmask & SOUND_MASK_LINE))
			mask |= 0x01;
		if (!(gus_recmask & SOUND_MASK_MIC))
			mask &= ~0x04;
	}
	mix_image &= ~0x07;
	mix_image |= mask & 0x07;
	outb(u_Mixer, mix_image);

	splx(flags);
}

int
gus_default_mixer_ioctl(int dev, u_int cmd, ioctl_arg arg)
{

#define MIX_DEVS	(SOUND_MASK_MIC|SOUND_MASK_LINE| \
			 SOUND_MASK_SYNTH|SOUND_MASK_PCM)

    if (((cmd >> 8) & 0xff) == 'M') {
	if (cmd & IOC_IN)
	    switch (cmd & 0xff) {
	    case SOUND_MIXER_RECSRC:
		gus_recmask = (*(int *) arg) & MIX_DEVS;
		if (!(gus_recmask & (SOUND_MASK_MIC | SOUND_MASK_LINE)))
		    gus_recmask = SOUND_MASK_MIC;
		/*
		 * Note! Input volumes are updated during
		 * next open for recording
		 */
		return *(int *) arg = gus_recmask;
		break;

	    case SOUND_MIXER_MIC:
		{
		    int             vol = (*(int *) arg) & 0xff;

		    if (vol < 0)
			vol = 0;
		    if (vol > 100)
			vol = 100;
		    gus_mic_vol = vol;
		    set_input_volumes();
		    return *(int *) arg = vol | (vol << 8);
		}
		break;

	    case SOUND_MIXER_LINE:
		{
		    int             vol = (*(int *) arg) & 0xff;

		    if (vol < 0)
			vol = 0;
		    if (vol > 100)
			vol = 100;
		    gus_line_vol = vol;
		    set_input_volumes();
		    return *(int *) arg = vol | (vol << 8);
		}
		break;

	    case SOUND_MIXER_PCM:
		    gus_pcm_volume = (*(int *) arg) & 0xff;
		    RANGE (gus_pcm_volume, 0, 100);
		    gus_sampling_update_volume();
		    return *(int *) arg = gus_pcm_volume | (gus_pcm_volume << 8);
		    break;

	    case SOUND_MIXER_SYNTH:
		    {
			    int             voice;

			    gus_wave_volume = (*(int *) arg) & 0xff;

			    RANGE (gus_wave_volume , 0, 100);

			    if (active_device == GUS_DEV_WAVE)
				    for (voice = 0; voice < nr_voices; voice++)
					    dynamic_volume_change(voice);	/* Apply the new vol */

			    return *(int *) arg = gus_wave_volume | (gus_wave_volume << 8);
		    }
		    break;

	    default:
		    return -(EINVAL);
	    }
    else
	    switch (cmd & 0xff) {	/* Return parameters */

	    case SOUND_MIXER_RECSRC:
		    return *(int *) arg = gus_recmask;
		    break;

	    case SOUND_MIXER_DEVMASK:
		    return *(int *) arg = MIX_DEVS;
		    break;

	    case SOUND_MIXER_STEREODEVS:
		    return *(int *) arg = 0;
		    break;

	    case SOUND_MIXER_RECMASK:
		    return *(int *) arg = SOUND_MASK_MIC | SOUND_MASK_LINE;
		    break;

	    case SOUND_MIXER_CAPS:
		    return *(int *) arg = 0;
		    break;

	    case SOUND_MIXER_MIC:
		    return *(int *) arg = gus_mic_vol | (gus_mic_vol << 8);
		    break;

	    case SOUND_MIXER_LINE:
		    return *(int *) arg = gus_line_vol | (gus_line_vol << 8);
		    break;

	    case SOUND_MIXER_PCM:
		    return *(int *) arg = gus_pcm_volume | (gus_pcm_volume << 8);
		    break;

	    case SOUND_MIXER_SYNTH:
		    return *(int *) arg = gus_wave_volume | (gus_wave_volume << 8);
		    break;

	    default:
		    return -(EINVAL);
	    }
} else
    return -(EINVAL);
}

static struct mixer_operations gus_mixer_operations = {"Gravis Ultrasound", gus_default_mixer_ioctl};

static void
gus_default_mixer_init()
{
if (num_mixers < MAX_MIXER_DEV)	/* Don't install if there is another
			     * mixer */
    mixer_devs[num_mixers++] = &gus_mixer_operations;

if (have_gus_max) {
    /*
     * Enable all mixer channels on the GF1 side. Otherwise
     * recording will not be possible using GUS MAX.
     */
    mix_image &= ~0x07;
    mix_image |= 0x04;	/* All channels enabled */
    outb(u_Mixer, mix_image);
}
}

/* start of pnp code */

static void 
SEND(int d, int r)
{
outb(PADDRESS, d);
outb(PWRITE_DATA, r);
}




/*
 * Get the device's serial number.  Returns 1 if the serial is valid.
 */
static int
get_serial(int rd_port, u_char *data)
{
	int             i, bit, valid = 0, sum = 0x6a;

	bzero(data, sizeof(char) * 9);

	for (i = 0; i < 72; i++) {
		bit = inb((rd_port << 2) | 0x3) == 0x55;
		DELAY(250);	/* Delay 250 usec */

		/* Can't Short Circuit the next evaluation, so 'and' is last */
		bit = (inb((rd_port << 2) | 0x3) == 0xaa) && bit;
		DELAY(250);	/* Delay 250 usec */

		valid = valid || bit;

		if (i < 64)
			sum = (sum >> 1) |
				(((sum ^ (sum >> 1) ^ bit) << 7) & 0xff);

		data[i / 8] = (data[i / 8] >> 1) | (bit ? 0x80 : 0);
	}
	valid = valid && (data[8] == sum);

	return valid;
}

static void
send_Initiation_LFSR()
{
	int             cur, i;

	/* Reset the LSFR */
	outb(PADDRESS, 0);
	outb(PADDRESS, 0);

	cur = 0x6a;
	outb(PADDRESS, cur);

	for (i = 1; i < 32; i++) {
		cur = (cur >> 1) | (((cur ^ (cur >> 1)) << 7) & 0xff);
		outb(PADDRESS, cur);
	}
}



static int 
isolation_protocol(int rd_port)
{
	int             csn;
	u_char   data[9];

	send_Initiation_LFSR();

	/* Reset CSN for All Cards */
	SEND(0x02, 0x04);

	for (csn = 1; (csn < MAX_CARDS); csn++) {
		/* Wake up cards without a CSN */

		SEND(WAKE, 0);
		SEND(SET_RD_DATA, rd_port);
		outb(PADDRESS, SERIAL_ISOLATION);
		DELAY(1000);	/* Delay 1 msec */
		if (get_serial(rd_port, data)) {
			printf("Board Vendor ID: %c%c%c%02x%02x",
			       ((data[0] & 0x7c) >> 2) + 64,
			       (((data[0] & 0x03) << 3) | ((data[1] & 0xe0) >> 5)) + 64,
			       (data[1] & 0x1f) + 64, data[2], data[3]);
			printf("     Board Serial Number: %08x\n", *(int *) &(data[4]));

			SEND(SET_CSN, csn);	/* Move this out of this
						 * function XXX */
			outb(PADDRESS, PSTATUS);


			return rd_port;
		} else
			break;
	}

	return 0;
}



static void 
IwaveDelay(WORD count)
{
	WORD            cur_cnt = 0, last_cnt;
	BYTE            reg, portb;

	count = 1193 * count;	/* convert number of ms to counter */
	last_cnt = count;
	portb = inb(0x61) & 0xFC;
	outb(0x61, portb);	/* disable counter */
	outb(0x43, 0xB0);	/* load LSB first then MSB */
	outb(0x42, (BYTE) count);
	outb(0x42, (BYTE) (count >> 8));
	outb(0x61, (BYTE) (portb | 0x01));	/* enable counter */
	while (cur_cnt <= count) {
		outb(0x43, 0x80);	/* latch counter */
		reg = inb(0x42);/* read latched value */
		cur_cnt = (((WORD) inb(0x42)) << 8) | reg;
		if (cur_cnt > last_cnt)
			break;
		last_cnt = cur_cnt;
	}
	outb(0x61, portb);	/* disable counter */
}

/*
 * ########################################################################
 * 
 * FUNCTION: IwaveStopDma
 * 
 * PROFILE: This function stops an active DMA transfer to or from the record or
 * playback FIFOs. Set the "path" variable to either PLAYBACK or RECORD.
 * ########################################################################
 */

static void 
IwaveStopDma(BYTE path)
{
	BYTE            reg;

	ENTER_CRITICAL;
	reg = inb(iw.pcodar) & 0xE0;
	outb(iw.pcodar, reg | _CFIG1I);	/* select CFIG1I */
	outb(iw.cdatap, (BYTE) (inb(iw.cdatap) & ~path));	/* disable playback path */
	LEAVE_CRITICAL;
}

/*
 * ########################################################################
 * 
 * FUNCTION : IwaveInputSource
 * 
 * PROFILE: This function allows the calling program to select among any of
 * several possible sources to the ADC's. The possible input sources and
 * their corresponding symbolic constants are: - Line        (LINE_IN) - Aux1
 * (AUX1_IN) - Microphone  (MIC_IN) - Mixer       (MIX_IN)
 * 
 * Set the first argument to either LEFT_SOURCE or RIGHT_SOURCE. Always use the
 * symbolic contants for the arguments.
 * 
 * ########################################################################
 */
static void 
IwaveInputSource(BYTE index, BYTE source)
{
	BYTE            reg;

	ENTER_CRITICAL;
	reg = inb(iw.pcodar) & 0xE0;
	outb(iw.pcodar, reg | index);	/* select register CLICI or CRICI */
	reg = inb(iw.cdatap) & ~MIX_IN;
	source &= MIX_IN;
	outb(iw.cdatap, (BYTE) (reg | source));
	LEAVE_CRITICAL;
}
static void 
IwavePnpGetCfg(void)
{
	WORD            val;


	ENTER_CRITICAL;
	IwavePnpDevice(AUDIO);
	outb(_PIDXR, 0x60);	/* select P2X0HI */
	val = ((WORD) inb(iw.pnprdp)) << 8;	/* get P2XR[9:8] */
	outb(_PIDXR, 0x61);	/* select P2XRLI */
	iw.p2xr = val + (WORD) inb(iw.pnprdp);	/* get P2XR[7:4] */

	outb(_PIDXR, 0x62);	/* select P3X0HI */
	val = ((WORD) inb(iw.pnprdp)) << 8;	/* get P3XR[9:8] */
	outb(_PIDXR, 0x63);	/* select P3X0LI */
	iw.p3xr = val + (WORD) inb(iw.pnprdp);	/* get P3XR[7:3] */

	outb(_PIDXR, 0x64);	/* select PHCAI */
	val = ((WORD) inb(iw.pnprdp)) << 8;	/* get PCODAR[9:8] */
	outb(_PIDXR, 0x65);	/* select PLCAI */
	iw.pcodar = val + (WORD) inb(iw.pnprdp);	/* get PCODAR[7:2] */

	outb(_PIDXR, 0x70);	/* select PUI1SI */
	iw.synth_irq = (WORD) (inb(iw.pnprdp) & 0x0F);	/* Synth IRQ number */

	outb(_PIDXR, 0x72);	/* select PUI2SI */
	iw.midi_irq = (WORD) (inb(iw.pnprdp) & 0x0F);	/* MIDI IRQ number */

	outb(_PIDXR, 0x74);	/* select PUD1SI */
	iw.dma1_chan = inb(iw.pnprdp) & 0x07;	/* DMA1 chan (LMC/Codec Rec) */

	outb(_PIDXR, 0x75);	/* select PUD2SI */
	iw.dma2_chan = inb(iw.pnprdp) & 0x07;	/* DMA2 chan (codec play) */


	IwavePnpDevice(EXT);	/* select external device */
	outb(_PIDXR, 0x60);	/* select PRAHI */
	val = ((WORD) inb(iw.pnprdp)) << 8;	/* get PCDRAR[9:8] */
	outb(_PIDXR, 0x61);	/* select PRALI */
	iw.pcdrar = val + (WORD) inb(iw.pnprdp);	/* get PCDRAR[7:4] */
	outb(_PIDXR, 0x62);	/* select PATAHI */
	val = ((WORD) inb(iw.pnprdp)) << 8;	/* get PATAAR[9:8] */
	outb(_PIDXR, 0x63);	/* select PATALI */
	iw.pataar = val + (WORD) inb(iw.pnprdp);	/* get PATAAR[7:1] */

	outb(_PIDXR, 0x70);	/* select PRISI */
	iw.ext_irq = (WORD) (inb(iw.pnprdp) & 0x0F);	/* Ext Dev IRQ number */

	outb(_PIDXR, 0x74);	/* select PRDSI */
	iw.ext_chan = inb(iw.pnprdp) & 0x07;	/* Ext Dev DMA channel */

	IwavePnpDevice(MPU401);	/* Select MPU401 Device */
	outb(_PIDXR, 0x60);	/* select P401HI */
	val = ((WORD) inb(iw.pnprdp)) << 8;	/* get P401AR[9:8] */
	outb(_PIDXR, 0x61);	/* select P401LI */
	iw.p401ar = val + (WORD) inb(iw.pnprdp);	/* get P401AR[7:1] */

	outb(_PIDXR, 0x70);	/* select PMISI */
	iw.mpu_irq = (WORD) (inb(iw.pnprdp) & 0x0F);	/* MPU401 Dev IRQ number */

	IwavePnpDevice(GAME);	/* Select GAME logical Device */
	outb(_PIDXR, 0x60);	/* select P201HI */
	val = ((WORD) inb(iw.pnprdp)) << 8;	/* get P201AR[9:8] */
	outb(_PIDXR, 0x61);	/* select P201LI */
	iw.p201ar = val + (WORD) inb(iw.pnprdp);	/* get P201AR[7:6] */

	IwavePnpDevice(EMULATION);	/* Select SB and ADLIB Device */
	outb(_PIDXR, 0x60);	/* select P388HI */
	val = ((WORD) inb(iw.pnprdp)) << 8;	/* get P388AR[9:8] */
	outb(_PIDXR, 0x61);	/* select P388LI */
	iw.p388ar = val + inb(iw.pnprdp);	/* get P388AR[7:6] */
	outb(_PIDXR, 0x70);	/* select PSBISI */
	iw.emul_irq = (WORD) (inb(iw.pnprdp) & 0x0F);	/* emulation Dev IRQ
							 * number */
	LEAVE_CRITICAL;
}

static void 
IwavePnpSetCfg(void)
{
	ENTER_CRITICAL;
	IwavePnpDevice(AUDIO);	/* select audio device */
	outb(_PIDXR, 0x60);	/* select P2X0HI */
	outb(_PNPWRP, (BYTE) (iw.p2xr >> 8));	/* set P2XR[9:8] */
	outb(_PIDXR, 0x61);	/* select P2X0LI */
	outb(_PNPWRP, (BYTE) iw.p2xr);	/* set P2XR[7:4] */
	/* P2XR[3:0]=0   */
	outb(_PIDXR, 0x62);	/* select P3X0HI */
	outb(_PNPWRP, (BYTE) (iw.p3xr >> 8));	/* set P3XR[9:8] */
	outb(_PIDXR, 0x63);	/* select P3X0LI */
	outb(_PNPWRP, (BYTE) (iw.p3xr));	/* set P3XR[7:3] */
	/* P3XR[2:0]=0   */
	outb(_PIDXR, 0x64);	/* select PHCAI */
	outb(_PNPWRP, (BYTE) (iw.pcodar >> 8));	/* set PCODAR[9:8] */
	outb(_PIDXR, 0x65);	/* select PLCAI */
	outb(_PNPWRP, (BYTE) iw.pcodar);	/* set PCODAR[7:2] */

	outb(_PIDXR, 0x70);	/* select PUI1SI */
	outb(_PNPWRP, (BYTE) (iw.synth_irq & 0x0F));	/* Synth IRQ number */
	outb(_PIDXR, 0x72);	/* select PUI2SI */
	outb(_PNPWRP, (BYTE) (iw.midi_irq & 0x0F));	/* MIDI IRQ number */

	outb(_PIDXR, 0x74);	/* select PUD1SI */
	outb(_PNPWRP, (BYTE) (iw.dma1_chan & 0x07));	/* DMA channel 1 */
	outb(_PIDXR, 0x75);	/* select PUD2SI */
	outb(_PNPWRP, (BYTE) (iw.dma2_chan & 0x07));	/* DMA channel 2 */

	IwavePnpDevice(EXT);
	outb(_PIDXR, 0x60);	/* select PRAHI */
	outb(_PNPWRP, (BYTE) (iw.pcdrar >> 8));	/* set PCDRAR[9:8] */
	outb(_PIDXR, 0x61);	/* select PRALI */
	outb(_PNPWRP, (BYTE) iw.pcdrar);	/* set PCDRAR[7:3] */
	/* PCDRAR[2:0]=0 */
	outb(_PIDXR, 0x62);	/* select PATAHI */
	outb(_PNPWRP, (BYTE) (iw.pataar >> 8));	/* set PATAAR[9:8] */
	outb(_PIDXR, 0x63);	/* select PATALI */
	outb(_PNPWRP, (BYTE) iw.pataar);	/* set PATAAR[7:1] */
	/* PATAAR[0]=0 */
	outb(_PIDXR, 0x70);	/* select PRISI */
	outb(_PNPWRP, (BYTE) (iw.ext_irq & 0x0F));	/* Ext Dev IRQ number */
	outb(_PIDXR, 0x74);	/* select PRDSI */
	outb(_PNPWRP, (BYTE) (iw.ext_chan & 0x07));	/* Ext Dev DMA channel */

	IwavePnpDevice(GAME);
	outb(_PIDXR, 0x60);	/* select P201HI */
	outb(_PNPWRP, (BYTE) (iw.p201ar >> 8));	/* set P201RAR[9:8] */
	outb(_PIDXR, 0x61);	/* select P201LI */
	outb(_PNPWRP, (BYTE) iw.p201ar);	/* set P201AR[7:6] */

	IwavePnpDevice(EMULATION);
	outb(_PIDXR, 0x60);	/* select P388HI */
	outb(_PNPWRP, (BYTE) (iw.p388ar >> 8));	/* set P388AR[9:8] */
	outb(_PIDXR, 0x61);	/* select P388LI */
	outb(_PNPWRP, (BYTE) iw.p388ar);	/* set P388AR[7:6] */

	outb(_PIDXR, 0x70);	/* select PSBISI */
	outb(_PNPWRP, (BYTE) (iw.emul_irq & 0x0F));	/* emulation IRQ number */

	IwavePnpDevice(MPU401);
	outb(_PIDXR, 0x60);	/* select P401HI */
	outb(_PNPWRP, (BYTE) (iw.p401ar >> 8));	/* set P401AR[9:8] */
	outb(_PIDXR, 0x61);	/* select P401LI */
	outb(_PNPWRP, (BYTE) iw.p401ar);	/* set P401AR[7:1] */

	outb(_PIDXR, 0x70);	/* select PMISI */
	outb(_PNPWRP, (BYTE) (iw.mpu_irq & 0x0F));	/* MPU emulation IRQ
							 * number */
	LEAVE_CRITICAL;
}

static void 
IwaveCfgIOSpace(void)
{
	ENTER_CRITICAL;
	IwavePnpDevice(AUDIO);	/* select audio device */
	outb(_PIDXR, 0x60);	/* select P2X0HI */
	outb(_PNPWRP, (BYTE) (iw.p2xr >> 8));	/* set P2XR[9:8] */
	outb(_PIDXR, 0x61);	/* select P2X0LI */
	outb(_PNPWRP, (BYTE) iw.p2xr);	/* set P2XR[7:4] */
	/* P2XR[3:0]=0   */
	outb(_PIDXR, 0x62);	/* select P3X0HI */
	outb(_PNPWRP, (BYTE) (iw.p3xr >> 8));	/* set P3XR[9:8] */
	outb(_PIDXR, 0x63);	/* select P3X0LI */
	outb(_PNPWRP, (BYTE) (iw.p3xr));	/* set P3XR[7:3] */
	/* P3XR[2:0]=0   */
	outb(_PIDXR, 0x64);	/* select PHCAI */
	outb(_PNPWRP, (BYTE) (iw.pcodar >> 8));	/* set PCODAR[9:8] */
	outb(_PIDXR, 0x65);	/* select PLCAI */
	outb(_PNPWRP, (BYTE) iw.pcodar);	/* set PCODAR[7:2] */

	IwavePnpDevice(EXT);
	outb(_PIDXR, 0x60);	/* select PRAHI */
	outb(_PNPWRP, (BYTE) (iw.pcdrar >> 8));	/* set PCDRAR[9:8] */
	outb(_PIDXR, 0x61);	/* select PRALI */
	outb(_PNPWRP, (BYTE) iw.pcdrar);	/* set PCDRAR[7:3] */
	/* PCDRAR[2:0]=0 */
	outb(_PIDXR, 0x62);	/* select PATAHI */
	outb(_PNPWRP, (BYTE) (iw.pataar >> 8));	/* set PATAAR[9:8] */
	outb(_PIDXR, 0x63);	/* select PATALI */
	outb(_PNPWRP, (BYTE) iw.pataar);	/* set PATAAR[7:1] */
	/* PATAAR[0]=0 */
	IwavePnpDevice(GAME);
	outb(_PIDXR, 0x60);	/* select P201HI */
	outb(_PNPWRP, (BYTE) (iw.p201ar >> 8));	/* set P201RAR[9:8] */
	outb(_PIDXR, 0x61);	/* select P201LI */
	outb(_PNPWRP, (BYTE) iw.p201ar);	/* set P201AR[7:6] */

	IwavePnpDevice(EMULATION);
	outb(_PIDXR, 0x60);	/* select P388HI */
	outb(_PNPWRP, (BYTE) (iw.p388ar >> 8));	/* set P388AR[9:8] */
	outb(_PIDXR, 0x61);	/* select P388LI */
	outb(_PNPWRP, (BYTE) iw.p388ar);	/* set P388AR[7:6] */

	IwavePnpDevice(MPU401);
	outb(_PIDXR, 0x60);	/* select P401HI */
	outb(_PNPWRP, (BYTE) (iw.p401ar >> 8));	/* set P401AR[9:8] */
	outb(_PIDXR, 0x61);	/* select P401LI */
	outb(_PNPWRP, (BYTE) iw.p401ar);	/* set P401AR[7:1] */
	LEAVE_CRITICAL;
}


/* ######################################################################## */
/* FILE: iwpnp.c */
/* */
/* REMARKS: This file contains the definitions for the InterWave's DDK */
/* functions dedicated to the configuration of the InterWave */
/* PNP logic. */
/* */
/* UPDATE: 4/07/95 */
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpKey */
/* */
/* PROFILE: This function issues the initiation key that places the PNP */
/* logic into configuration mode. The PNP logic is quiescent at */
/* power up and must be enabled by software. This function will */
/* do 32 I/O writes to the PIDXR (0x0279). The function will */
/* first reset the LFSR to its initial value by a sequence of two */
/* write cycles of 0x00 to PIDXR before issuing the key. */
/* */
/* ######################################################################## */
static void 
IwavePnpKey(void)
{
	/* send_Initiation_LFSR(); */

	BYTE            code = 0x6A;
	BYTE            msb;
	BYTE            i;

	/* ############################################### */
	/* Reset Linear Feedback Shift Reg. */
	/* ############################################### */
	outb(0x279, 0x00);
	outb(0x279, 0x00);

	outb(0x279, code);	/* Initial value */

	for (i = 1; i < 32; i++) {
		msb = ((code & 0x01) ^ ((code & 0x02) >> 1)) << 7;
		code = (code >> 1) | msb;
		outb(0x279, code);
	}

}

static BYTE 
IwavePnpIsol(PORT * pnpread)
{
	int             num_pnp_devs;
	int             rd_port = 0;
	printf("Checking for GUS Plug-n-Play ...\n");

	/* Try various READ_DATA ports from 0x203-0x3ff */
	for (rd_port = 0x80; (rd_port < 0xff); rd_port += 0x10) {
		if (0)
			printf("Trying Read_Port at %x\n",
			       (rd_port << 2) | 0x3);

		num_pnp_devs = isolation_protocol(rd_port);
		if (num_pnp_devs) {
			*pnpread = rd_port << 2 | 0x3;
			break;
		}
	}
	if (!num_pnp_devs) {
		printf("No Plug-n-Play devices were found\n");
		return 0;
	}
	return 1;
}

/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpSerial */
/* */
/* PROFILE: This function reads the first nine bytes of the data from */
/* the serial EEPROM and returns the Vendor ID and the serial */
/* number. First, it resets the EEPROM control logic by */
/* issuing a WAKE[CSN] command. The function will return an */
/* ASCII string for the vendor ID into the char array pointed */
/* to by "vendor" in the VVVNNNN format. The serial number */
/* is placed in the 32-bit variable pointed to by "serial". */
/* Note that the 9th byte is read but not used as it is invalid */
/* when the serial identifier is read via PRESDI. */
/* */
/* This function assumes that the PNP state machine is not in */
/* the "wait for key state". Otherwise, unpredictable results */
/* will be obtained. */
/* */
/* ######################################################################## */
static void
IwavePnpSerial(PORT pnprdp,
	       BYTE csn,
	       BYTE * vendor,
	       DWORD * serial)
{
	BYTE            presdi, digit, i;

	*serial = 0L;

	/* ####################################### */
	/* Reset Serial EEPROM logic */
	/* ####################################### */
	IwavePnpWake(csn);	/* Wake card up */

	for (i = 1; i <= 4; i++) {
		IwavePnpPeek(pnprdp, 1, &presdi);

		switch (i) {
		case 1:
			*(vendor++) = ((presdi & 0x7C) >> 2) | 0x40;	/* 1st char */
			*vendor = (presdi & 0x03) << 3;	/* isolate bits[4:3] of
							 * 2nd char */
			break;
		case 2:
			*vendor = ((presdi & 0xE0) >> 5) | (*vendor);
			*(vendor++) = (*vendor) | 0x40;	/* 2nd char */
			*vendor = (presdi & 0x1F) | 0x40;	/* 3rd char */
			break;
		case 3:
		case 4:
			digit = (presdi & 0xF0) >> 4;
			if (digit <= 0x09)
				*(++vendor) = digit + 0x30;	/* ASCII of digit */
			else
				*(++vendor) = (digit & 0x07) + 0x3F;
			digit = presdi & 0x0F;
			if (digit <= 0x09)
				*(++vendor) = digit + 0x30;
			else
				*(++vendor) = (digit & 0x07) + 0x3F;
			break;
		}
	}
	*(++vendor) = '\0';
	IwavePnpPeek(pnprdp, 4, (BYTE *) serial);
	IwavePnpPeek(pnprdp, 1, NULL);	/* discard checksum */
}
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpPeek */
/* */
/* PROFILE: This function will return the number of specified bytes of */
/* resource data from the serial EEPROM. The function will NOT */
/* reset the serial EEPROM logic to allow reading the entire */
/* EEPROM by issuing repeated calls. The caller must supply a */
/* pointer to where the data are to be stored. */
/* It is assumed that the InterWave is not in either "sleep" */
/* or "wait for key" states. Note that on the first call, if */
/* the caller means to read from the beggining of data the */
/* serial EEPROM logic must be reset. For this, the caller */
/* should issue a WAKE[CSN] command */
/* */
/* ######################################################################## */
static void 
IwavePnpPeek(PORT pnprdp, WORD bytes, BYTE * data)
{
	WORD            i;
	BYTE            datum;

	for (i = 1; i <= bytes; i++) {
		outb(_PIDXR, 0x05);	/* select PRESSI */

		while (TRUE) {	/* wait til new data byte is ready */
			if (inb(pnprdp) & PNP_DATA_RDY)
				break;	/* new resource byte ready */
		}
		outb(_PIDXR, 0x04);	/* select PRESDI */
		datum = inb(pnprdp);	/* read resource byte */
		if (data != NULL)
			*(data++) = datum;	/* store it */
	}
}
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpEeprom */
/* */
/* PROFILE: This function allows the caller to control the serial */
/* EEPROM directly while the audio device is inactive. To */
/* de-activate the audio device issue the call */
/* IwavePnpActivate(AUDIO,OFF). */
/* */
/* ######################################################################## */
static void 
IwavePnpEeprom(BYTE ctrl)
{
	ENTER_CRITICAL;
	outb(_PIDXR, 0xF1);	/* select PSECI */
	outb(_PNPWRP, ctrl);	/* write PSECI */
	LEAVE_CRITICAL;
}
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpActivate */
/* */
/* PROFILE: This function will activate or de-activate the audio device */
/* or the external device on the InterWave. Set the "dev" arg */
/* to AUDIO for the audio device or EXT for the external device. */
/* Set "bool" to ON or OFF to turn the device on or off the ISA */
/* bus. Notice that for a logical device to work, it must be */
/* activated. */
/* */
/* ######################################################################## */
static void 
IwavePnpActivate(BYTE dev, BYTE bool)
{
	IwavePnpDevice(dev);	/* select audio device */
	ENTER_CRITICAL;
	outb(_PIDXR, ACTIVATE_DEV);	/* select Activate Register */
	outb(_PNPWRP, bool);	/* write register */
	LEAVE_CRITICAL;

}
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpDevice */
/* */
/* PROFILE: This function allows the caller to select between five */
/* logical devices available on the InterWave.It is assumed */
/* that the PNP state machine is in configuration mode. */
/* */
/* ######################################################################## */
static void 
IwavePnpDevice(BYTE dev)
{
	ENTER_CRITICAL;
	outb(_PIDXR, _PLDNI);	/* select PLDNI */
	outb(_PNPWRP, dev);	/* write PLDNI */
	LEAVE_CRITICAL;
}
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpPower */
/* */
/* PROFILE: This function allows the caller to disable major sections of */
/* the InterWave to prevent them from consuming power and */
/* loading the ISA bus. */
/* */
/* It is assumed that the PNP state machine is in configuration */
/* mode. */
/* */
/* ######################################################################## */
static void 
IwavePnpPower(BYTE mode)
{
	ENTER_CRITICAL;
	outb(_PIDXR, _PPWRI);	/* select PPWRI */
	outb(_PNPWRP, mode);	/* write PPWRI */
	LEAVE_CRITICAL;
}
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpWake */
/* */
/* PROFILE: This function issues a WAKE[CSN] command to the InterWave. If */
/* the CSN matches the PNP state machine will enter the */
/* configuration state. Otherwise it will enter the sleep mode. */
/* */
/* It is assumed that the PNP state machine is not in the */
/* "wait for key" state. */
/* */
/* ######################################################################## */
static void 
IwavePnpWake(BYTE csn)
{
	ENTER_CRITICAL;
	outb(_PIDXR, _PWAKEI);	/* select PWAKEI */
	outb(_PNPWRP, csn);	/* write csn */
	LEAVE_CRITICAL;
}
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpIOcheck */
/* */
/* PROFILE: This function allows the caller to perform a conflict check */
/* on an I/O port to be used by a logical device. The function */
/* receives the base address of the I/O range as well as the */
/* number of ports in the range and then performs the I/O check */
/* protocol. It returns the address of the port if a conflict */
/* is detected or IO_CHK if no conflict is detected. */
/* */
/* This function assumes that the logical device has been de- */
/* activated and that the PNP state machine is in config mode. */
/* */
/* ######################################################################## */
static PORT 
IwavePnpIOcheck(PORT base, BYTE no_ports)
{
	BYTE            i;
	PORT            portid;

	outb(_PIDXR, RANGE_IOCHK);	/* select IO range check reg */

	for (i = 0; i < no_ports; i++) {
		portid = base + i;	/* port to check */
		outb(_PNPWRP, 0x02);	/* must drive 0xAA onto bus */
		if (inb(portid) != 0xAA)
			return (portid);	/* IO conflict detected */

		outb(_PNPWRP, 0x03);	/* must drive 0x55 onto bus */
		if (inb(portid) != 0x55)
			return (portid);	/* IO conflict detected */
	}
	return (IO_OK);
}
/* ######################################################################## */
/* */
/* FUNCTION: IwavePnpGetCSN */
/* */
/* PROFILE: This function allows the caller to detect an InterWave based */
/* adapter board and will return its asigned CSN so that an */
/* an application can access its PnP interface and determine the */
/* borad's current configuration. In conducting its search for */
/* the InterWave IC, the function will use the first 32 bits of */
/* the Serial Identifier called the vendor ID in the PnP ISA */
/* spec. The last 4 bits in the Vendor ID represent a revision */
/* number for the particular product and this function gives the */
/* caller the option of taking this revision number into account */
/* or not in the search. If the function fails to find the */
/* InterWave IC it will return FALSE. */
/* */
/* ######################################################################## */
static BYTE 
IwavePnpGetCSN(DWORD VendorID, BYTE csn_max)
{
	BYTE            csn;
	DWORD           vendor;

	IwavePnpKey();		/* Key to access PnP Interface */
	VendorID &= (0xFFFFFFF0);	/* reset 4 least significant bits */

	for (csn = 1; csn <= csn_max; csn++) {
		IwavePnpWake(csn);	/* Select card */
		IwavePnpPeek(iw.pnprdp, 4, (BYTE *) & vendor);	/* get vendor ID */
		vendor &= (0xFFFFFFF0);
		if (vendor == VendorID) {	/* If IDs match, InterWave is
						 * found */
			outb(_PIDXR, 0x02);	/* Place all cards in
						 * wait-for-key state */
			outb(0x0A79, 0x02);
			return (csn);
		}
	}
	outb(_PIDXR, 0x02);	/* Place all cards in wait-for-key state */
	outb(0x0A79, 0x02);
	return (FALSE);		/* InterWave IC not found */
}
/* ######################################################################## */
/* */
/*
 * FUNCTION: IwavePnpPing
 */
/* */
/* PROFILE: This function allows the caller to detect an InterWave based */
/* adapter board and will return its asigned CSN so that an */
/* an application can access its PnP interface and determine the */
/* borad's current configuration. In conducting its search for */
/* the InterWave IC, the function will use the first 32 bits of */
/* the Serial Identifier called the vendor ID in the PnP ISA */
/* spec. The last 4 bits in the Vendor ID represent a revision */
/* number for the particular product and will not be included */
/* in the search. The function will return the Vendor ID and the */
/* calling application should check the revision bits to make */
/* sure they are compatible with the board. */
/* */
/* ######################################################################## */
static BYTE 
IwavePnpPing(DWORD VendorID)
{
	BYTE            csn;

	VendorID &= (0xFFFFFFF0);	/* reset 4 least significant bits */
	IwavePnpKey();		/* Key to access PnP Interface */
	while (iw.pnprdp <= 0x23F) {
		for (csn = 1; csn <= 10; csn++) {
			IwavePnpWake(csn);	/* Select card */
			IwavePnpPeek(iw.pnprdp, 4, (BYTE *) & iw.vendor);	/* get vendor ID */


			if (((iw.vendor) & 0xFFFFFFF0) == VendorID) {	/* If IDs match,
									 * InterWave is found */

				outb(_PIDXR, 0x02);	/* Place all cards in
							 * wait-for-key state */
				outb(0x0A79, 0x02);
				return (csn);
			}
		}
		iw.pnprdp += 0x04;
	}
	outb(_PIDXR, 0x02);	/* Place all cards in wait-for-key state */
	outb(0x0A79, 0x02);
	return (FALSE);		/* InterWave IC not found */
}

/* end of pnp code */

static WORD 
IwaveMemSize(void)
{
	BYTE            datum = 0x55;
	ADDRESS         local = 0L;

	outb(iw.igidxr, _LMCI);
	outb(iw.i8dp, inb(iw.i8dp) & 0xFD);	/* DRAM I/O cycles selected */

	while (TRUE) {
		IwaveMemPoke(local, datum);
		IwaveMemPoke(local + 1L, datum + 1);
		if (IwaveMemPeek(local) != datum || IwaveMemPeek(local + 1L) != (datum + 1) || IwaveMemPeek(0L) != 0x55)
			break;
		local += RAM_STEP;
		datum++;
	}
	return ((WORD) (local >> 10));
}

static BYTE 
IwaveMemPeek(ADDRESS addr)
{
	PORT            p3xr;

	p3xr = iw.p3xr;


	outb(iw.igidxr, 0x43);	/* Select LMALI */
	outw(iw.i16dp, (WORD) addr);	/* Lower 16 bits of LM */
	outb(iw.igidxr, 0x44);	/* Select LMAHI */
	outb(iw.i8dp, (BYTE) (addr >> 16));	/* Upper 8 bits of LM */
	return (inb(iw.lmbdr));	/* return byte from LMBDR */
}


static void 
IwaveMemPoke(ADDRESS addr, BYTE datum)
{
	PORT            p3xr;
	p3xr = iw.p3xr;


	outb(iw.igidxr, 0x43);	/* Select LMALI */
	outw(iw.i16dp, (WORD) addr);	/* Lower 16 bits of LM */
	outb(iw.igidxr, 0x44);	/* Select LMAHI */
	outb(iw.i8dp, (BYTE) (addr >> 16));	/* Upper 8 bits of LM */
	outb(iw.lmbdr, datum);	/* Write byte to LMBDR */
}

/* ######################################################################## */
/* */
/* FUNCTION: IwaveMemCfg */
/* */
/* PROFILE : This function determines the amount of DRAM from its */
/* configuration accross all banks. It sets the configuration */
/* into register LMCFI and stores the total amount of DRAM */
/* into iw.size_mem (Kbytes). */
/* */
/* The function first places the IC in enhanced mode to allow */
/* full access to all DRAM locations. Then it selects full */
/* addressing span (LMCFI[3:0]=0x0C). Finally, it determines */
/* the amount of DRAM in each bank and from this the actual */
/* configuration. */
/* */
/* Note that if a configuration other than one indicated in */
/* the manual is implemented, this function will select */
/* full addressing span (LMCFI[3:0]=0xC). */
/* */
/* ######################################################################## */
static void 
IwaveMemCfg(DWORD * lpbanks)
{
	DWORD           bank[4] = {0L, 0L, 0L, 0L};
	DWORD           addr = 0L, base = 0L, cnt = 0L;
	BYTE            i, reg, ram = FALSE;
	WORD            lmcfi;
	/* */
	ENTER_CRITICAL;
	outb(iw.igidxr, 0x99);
	reg = inb(iw.i8dp);	/* image of sgmi */
	outb(iw.igidxr, 0x19);
	outb(iw.i8dp, (BYTE) (reg | 0x01));	/* enable enhaced mode */
	outb(iw.igidxr, _LMCFI);/* select LM Conf Reg */
	lmcfi = inw(iw.i16dp) & 0xFFF0;
	outw(iw.i16dp, lmcfi | 0x000C);	/* max addr span */
	/* */
	/* Clear every RAM_STEPth location */
	/* */
	while (addr < RAM_MAX) {
		IwaveMemPoke(addr, 0x00);
		addr += RAM_STEP;
	}
	/* */
	/* Determine amount of RAM in each bank */
	/* */
	for (i = 0; i < 4; i++) {
		IwaveMemPoke(base, 0xAA);	/* mark start of bank */
		IwaveMemPoke(base + 1L, 0x55);
		if ((IwaveMemPeek(base) == 0xAA) && (IwaveMemPeek(base + 1L) == 0x55))
			ram = TRUE;
		if (ram) {
			while (cnt < BANK_MAX) {
				bank[i] += RAM_STEP;
				cnt += RAM_STEP;
				addr = base + cnt;
				if (IwaveMemPeek(addr) == 0xAA)
					break;
			}
		}
		if (lpbanks != NULL) {
			*lpbanks = bank[i];
			lpbanks++;
		}
		bank[i] = bank[i] >> 10;
		base += BANK_MAX;
		cnt = 0L;
		ram = FALSE;
	}
	/* */
	iw.flags &= ~DRAM_HOLES;
	outb(iw.igidxr, _LMCFI);
	if (bank[0] == 256 && bank[1] == 0 && bank[2] == 0 && bank[3] == 0)
		outw(iw.i16dp, lmcfi);
	else if (bank[0] == 256 && bank[1] == 256 && bank[2] == 0 && bank[3] == 0)
		outw(iw.i16dp, lmcfi | 0x01);
	else if (bank[0] == 256 && bank[1] == 256 && bank[2] == 256 && bank[3] == 256)
		outw(iw.i16dp, lmcfi | 0x02);
	else if (bank[0] == 256 && bank[1] == 1024 && bank[2] == 0 && bank[3] == 0)
		outw(iw.i16dp, lmcfi | 0x03);
	else if (bank[0] == 256 && bank[1] == 1024 && bank[2] == 1024 && bank[3] == 1024)
		outw(iw.i16dp, lmcfi | 0x04);
	else if (bank[0] == 256 && bank[1] == 256 && bank[2] == 1024 && bank[3] == 0)
		outw(iw.i16dp, lmcfi | 0x05);
	else if (bank[0] == 256 && bank[1] == 256 && bank[2] == 1024 && bank[3] == 1024)
		outw(iw.i16dp, lmcfi | 0x06);
	else if (bank[0] == 1024 && bank[1] == 0 && bank[2] == 0 && bank[3] == 0)
		outw(iw.i16dp, lmcfi | 0x07);
	else if (bank[0] == 1024 && bank[1] == 1024 && bank[2] == 0 && bank[3] == 0)
		outw(iw.i16dp, lmcfi | 0x08);
	else if (bank[0] == 1024 && bank[1] == 1024 && bank[2] == 1024 && bank[3] == 1024)
		outw(iw.i16dp, lmcfi | 0x09);
	else if (bank[0] == 4096 && bank[1] == 0 && bank[2] == 0 && bank[3] == 0)
		outw(iw.i16dp, lmcfi | 0x0A);
	else if (bank[0] == 4096 && bank[1] == 4096 && bank[2] == 0 && bank[3] == 0)
		outw(iw.i16dp, lmcfi | 0x0B);
	else			/* Flag the non-contiguous config of memory */
		iw.flags |= DRAM_HOLES;
	/* */
	outb(iw.igidxr, 0x19);	/* restore sgmi */
	outb(iw.i8dp, reg);
	LEAVE_CRITICAL;
}


/* ######################################################################## */
/**/
/* FUNCTION: IwaveCodecIrq */
/**/
/* PROFILE: This function disables or enables the Codec Interrupts. To */
/* enable interrupts set CEXTI[2] high thus causing all interrupt */
/* sources (CSR3I[6:4]) to pass onto the IRQ pin. To disable */
/* interrupts set CEXTI[1]=0. To enable Code IRQs issue this call: */
/**/
/* IwaveCodecIrq(CODEC_IRQ_ENABLE). To disable IRQs issue the call */
/**/
/* IwaveCodeIrq(~CODEC_IRQ_ENABLE). */
/**/
/* ######################################################################## */
static void 
IwaveCodecIrq(BYTE mode)
{
	BYTE            reg;

	ENTER_CRITICAL;
	reg = inb(iw.pcodar) & 0xE0;
	outb(iw.pcodar, reg | _CSR3I);	/* select CSR3I */
	outb(iw.cdatap, 0x00);	/* clear all interrupts */
	outb(iw.pcodar + 0x02, 0x00);	/* clear CSR1R */
	outb(iw.pcodar, reg | _CEXTI);	/* select CEXTI */
	reg = inb(iw.cdatap);
	if (mode == CODEC_IRQ_ENABLE)	/* enable Codec Irqs */
		outb(iw.cdatap, (BYTE) (reg | CODEC_IRQ_ENABLE));
	else			/* disable Codec Irqs */
		outb(iw.cdatap, (BYTE) (reg & ~CODEC_IRQ_ENABLE));
	LEAVE_CRITICAL;
}


/* ######################################################################### */
/**/
/* FUNCTION: IwaveRegPeek */
/**/
/* PROFILE : This function returns the value stored in any readable */
/* InterWave register. It takes as input a pointer to a */
/* structure containing the addresses of the relocatable I/O */
/* space as well as a register mnemonic. To correctly use this */
/* function, the programmer must use the mnemonics defined in */
/* "iwdefs.h". These mnemonics contain coded information used */
/* by the function to properly access the desired register. */
/**/
/* An attempt to read from a write-only register will return */
/* meaningless data. */
/**/
/* ######################################################################### */
static WORD 
IwaveRegPeek(DWORD reg_mnem)
{
	BYTE            index, val;
	WORD            reg_id, offset;

	offset = (WORD) ((BYTE) reg_mnem);
	reg_id = (WORD) (reg_mnem >> 16);
	index = (BYTE) (reg_mnem >> 8);

	/* ################################################### */
	/* Logic to read registers in P2XR block & GMCR */
	/* ################################################### */

	if (reg_id >= 0x0001 && reg_id <= 0x001A) {	/* UMCR to GMCR */
		if (reg_id <= 0x000E)	/* UMCR to USRR */
			return ((WORD) inb(iw.p2xr + offset));

		if (reg_id == 0x0019)
			return ((WORD) inb(iw.p201ar));

		else {		/* GUS Hidden registers or GMCR */
			BYTE            iveri;

			outb(iw.igidxr, 0x5B);	/* select IVERI */
			iveri = inb(iw.i8dp);	/* read IVERI */
			outb(iw.i8dp, (BYTE) (iveri | 0x09));	/* set IVERI[3,0] */
			if (reg_id == 0x001A) {	/* GMCR */
				val = inb(iw.p3xr);
				outb(iw.i8dp, iveri);	/* restore IVERI */
				return ((WORD) val);
			}
			val = inb(iw.p2xr + 0x0F);	/* read URCR */
			val = (val & 0xF8) | index;	/* value for URCR[2:0] */
			outb(iw.p2xr + 0x0F, val);	/* set URCR[2:0] */

			if (reg_mnem == UDCI || reg_mnem == UICI) {
				val = inb(iw.p2xr);
				if (reg_mnem == UDCI)
					outb(iw.p2xr, (BYTE) (val & 0xBF));
				else
					outb(iw.p2xr, (BYTE) (val | 0x40));
			}
			val = inb(iw.p2xr + 0x0B);
			outb(iw.igidxr, 0x5B);	/* select IVERI */
			outb(iw.i8dp, iveri);	/* restore IVERI */
			return ((WORD) val);	/* read register */
		}
	}
	/* ################################################### */
	/* Logic to read registers in P3XR block */
	/* ################################################### */

	if (reg_id >= 0x001B && reg_id <= 0x005C) {	/* GMSR to LMBDR */
		if (reg_id == 0x005C)	/* LMBDR */
			return ((WORD) inb(iw.lmbdr));

		if (reg_id >= 0x001B && reg_id <= 0x0021)	/* GMSR to I8DP */
			if (offset == 0x04)
				return (inw(iw.i16dp));
			else
				return ((WORD) inb(iw.p3xr + offset));
		else {		/* indexed registers */

			if (reg_id <= 0x003F)
				index |= 0x80;	/* adjust for reading */

			outb(iw.igidxr, index);	/* select register */

			if (offset == 0x04)
				return (inw(iw.i16dp));
			else
				return ((WORD) inb(iw.i8dp));
		}
	}
	/* #################################################### */
	/* Logic to read registers in PCODAR block */
	/* #################################################### */

	if (reg_id >= 0x005D && reg_id <= 0x0081) {	/* CIDXR to CLRCTI */
		if (reg_id <= 0x0061)
			return ((WORD) inb(iw.pcodar + offset));	/* CRDR */

		else {		/* indexed registers */
			BYTE            cidxr;

			cidxr = inb(iw.pcodar);
			cidxr = (cidxr & 0xE0) + index;
			outb(iw.pcodar, cidxr);	/* select register */
			return ((WORD) inb(iw.cdatap));
		}
	}
	/* ##################################################### */
	/* Logic to read the PnP registers */
	/* ##################################################### */
	if (reg_id >= 0x0082 && reg_id <= 0x00B7) {	/* PCSNBR to PMITI */
		if (reg_id == 0x0085)
			return ((WORD) inb(iw.pnprdp));

		if (reg_id < 0x0085)
			return ((WORD) inb((WORD) reg_mnem));

		else {		/* indexed registers */
			if (reg_id >= 0x008E && reg_id <= 0x00B7) {
				outb(0x0279, 0x07);	/* select PLDNI */
				outb(0xA79, (BYTE) offset);	/* select logical dev */
			}
			outb(0x0279, index);	/* select the register */
			return ((WORD) inb(iw.pnprdp));
		}
	}
	return 0;
}
/* ######################################################################### */
/**/
/* FUNCTION: IwaveRegPoke */
/**/
/* PROFILE : This function writes a value to any writable */
/* InterWave register. It takes as input a pointer to a */
/* structure containing the addresses of the relocatable I/O */
/* space as well as a register mnemonic. To correctly use this */
/* function, the programmer must use the mnemonics defined in */
/* "iwdefs.h". These mnemonics contain coded information used */
/* by the function to properly access the desired register. */
/**/
/* This function does not guard against writing to read-only */
/* registers. It is the programmer's responsibility to ensure */
/* that the writes are to valid registers. */
/**/
/* ######################################################################### */
static void 
IwaveRegPoke(DWORD reg_mnem, WORD datum)
{
	BYTE            index;
	BYTE            val;
	WORD            reg_id;
	WORD            offset;

	offset = (WORD) ((BYTE) reg_mnem);
	reg_id = (WORD) (reg_mnem >> 16);
	index = (BYTE) (reg_mnem >> 8);


	/* ####################################################### */
	/* Logic to write to registers in P2XR block */
	/* ####################################################### */
	if (reg_id >= 0x0001 && reg_id <= 0x0019) {	/* UMCR to GGCR */
		if (reg_id <= 0x000E) {	/* UMCR to USRR */
			outb(iw.p2xr + offset, (BYTE) datum);
			return;
		}
		if (reg_id == 0x0019) {
			outb(iw.p201ar, (BYTE) datum);
			return;
		} else {	/* GUS Hidden registers */

			BYTE            iveri;

			outb(iw.igidxr, 0x5B);	/* select IVERI */
			iveri = inb(iw.i8dp);	/* read IVERI */
			outb(iw.i8dp, (BYTE) (iveri | 0x09));	/* set IVERI[3,0] */
			val = inb(iw.p2xr + 0x0F);	/* read URCR */
			val = (val & 0xF8) | index;	/* value for URCR[2:0] */
			outb(iw.p2xr + 0x0F, val);	/* set URCR[2:0] */

			if (reg_mnem == UDCI || reg_mnem == UICI) {
				val = inb(iw.p2xr);	/* read UMCR */
				if (reg_mnem == UDCI)
					outb(iw.p2xr, (BYTE) (val & 0xBF));	/* set UMCR[6]=0 */
				else
					outb(iw.p2xr, (BYTE) (val | 0x40));	/* set UMCR[6]=1 */
			}
			outb(iw.p2xr + 0x0B, (BYTE) datum);	/* write register */
			outb(iw.igidxr, 0x5B);	/* select IVERI */
			outb(iw.i8dp, iveri);	/* restore IVERI */
			return;
		}
	}
	/* ############################################################# */
	/* Logic to write to registers in P3XR block */
	/* ############################################################# */

	if (reg_id >= 0x001A && reg_id <= 0x005C) {	/* GMCR to LMBDR */

		if (reg_id == 0x005C) {	/* LMBDR */
			outb(iw.lmbdr, (BYTE) datum);
			return;
		}
		if (reg_id == 0x001B)	/* GMSR */
			return;

		if (reg_id >= 0x001A && reg_id <= 0x0021)	/* GMCR to I8DP */
			if (offset == 0x04)
				outw(iw.i16dp, datum);
			else
				outb(iw.p3xr + offset, (BYTE) datum);
		else {		/* indexed registers */
			outb(iw.igidxr, index);	/* select register */

			if (offset == 0x04)
				outw(iw.i16dp, datum);
			else
				outb(iw.i8dp, (BYTE) datum);
		}
	}
	/* /################################################### */
	/* Logic to write to registers in PCODAR block */
	/* ################################################### */

	if (reg_id >= 0x005C && reg_id <= 0x0081) {	/* CIDXR to CLRCTI */
		if (reg_id <= 0x0061)
			outb(iw.pcodar + offset, (BYTE) datum);

		else {		/* one of the indexed registers */
			BYTE            cidxr;

			cidxr = inb(iw.pcodar);
			cidxr = (cidxr & 0xE0) + index;
			outb(iw.pcodar, cidxr);	/* select register */
			outb(iw.cdatap, (BYTE) datum);
		}
	}
	/* ###################################################### */
	/* Logic to write to the PnP registers */
	/* ###################################################### */
	if (reg_id >= 0x0082 && reg_id <= 0x00B7) {
		if (reg_id == 0x0085) {
			outb(iw.pnprdp, (BYTE) datum);
			return;
		}
		if (reg_id < 0x0085)
			outb((WORD) reg_mnem, (BYTE) datum);

		else {		/* one of the indexed registers */
			if (reg_id >= 0x008E && reg_id <= 0x00B7) {
				outb(0x0279, 0x07);	/* select PLDNI */
				outb(0xA79, (BYTE) offset);	/* select logical dev */
			}
			outb(0x0279, index);	/* select the register */
			outb(0xA79, (BYTE) datum);
		}
	}
}


static void 
IwaveLineLevel(char level, char index)
{
	char            reg;

	level &= 0x1F;

	ENTER_CRITICAL;
	reg = inb(iw.pcodar) & 0xE0;
	outb(iw.pcodar, reg | index);	/* select register */
	outb(iw.cdatap, (BYTE) ((inb(iw.cdatap) & 0x80) | level));	/* set level */
	LEAVE_CRITICAL;
}

static void 
IwaveCodecMode(char mode)
{
    char            reg;

    ENTER_CRITICAL;
    reg = inb(iw.pcodar) & 0xE0;
    outb(iw.pcodar, reg | _CMODEI);	/* select CMODEI */
    outb(iw.cdatap, mode);
    LEAVE_CRITICAL;
    iw.cmode = mode;
}

static void 
IwaveLineMute(BYTE mute, BYTE inx)
{
    BYTE            reg;

    ENTER_CRITICAL;
    reg = inb(iw.pcodar) & 0xE0;
    outb(iw.pcodar, reg | inx);	/* select register */
    if (mute == ON)
	outb(iw.cdatap, (BYTE) (inb(iw.cdatap) | 0x80));	/* mute */
    else
	outb(iw.cdatap, (BYTE) (inb(iw.cdatap) & 0x7F));	/* unmute */
    LEAVE_CRITICAL;
}

static void 
Iwaveinitcodec()
{

	u_short  iwl_codec_base = iw.pcodar;
	u_short  iwl_codec_data = iw.pcodar + 1;
	u_short  foo;



	/*
	 * Set the CEXTI register foo = CODEC_CEXTI_DEFAULT;
	 * IWL_CODEC_OUT(EXTERNAL_CONTROL, foo);
	 */
	/*
	 * Disable Interrupts iwl_codec_disable_irqs();
	 */

	/* Set the CODEC to Operate in Mode 3 */
	IWL_CODEC_OUT(MODE_SELECT_ID, 0x6C);
	foo = inb(iwl_codec_data);

	/* Set the configuration registers to their default values */
	foo = CODEC_CFIG1I_DEFAULT;
	IWL_CODEC_OUT(CONFIG_1 | CODEC_MCE, foo);
	outb(iwl_codec_base, CONFIG_1);
	foo = CODEC_CFIG2I_DEFAULT;
	IWL_CODEC_OUT(CONFIG_2, foo);

	foo = CODEC_CFIG3I_DEFAULT;
	IWL_CODEC_OUT(CONFIG_3, foo);

}



int 
IwaveOpen(char voices, char mode, struct address_info * hw)
{

	u_long   flags;
	u_char   tmp;

	flags = splhigh();

	iw.pnprdp = 0;
	if (IwavePnpIsol(&iw.pnprdp)) {

		iw.vendor = GUS_PNP_ID;

		iw.csn = IwavePnpPing(iw.vendor);

		IwavePnpKey();

		IwavePnpWake(iw.csn);

		IwavePnpGetCfg();
		IwavePnpKey();

		IwavePnpWake(iw.csn);
	}
	if (hw->irq > 0) {
		/* I see the user wants to set the GUS PnP */
		/* Okay lets do it */
		iw.csn = 1;
		iw.p2xr = hw->io_base;
		iw.p3xr = hw->io_base + 0x100;
		iw.pcodar = hw->io_base + 0x10c;

		iw.synth_irq = hw->irq;

		iw.midi_irq = hw->irq;

		iw.dma1_chan = hw->dma;

		if (hw->dma2 == -1) {
			iw.dma2_chan = hw->dma;
		} else {
			iw.dma2_chan = hw->dma2;
		}


	} else {

		/* tell the os what we are doing 8) */
		hw->io_base = iw.p2xr;
		hw->irq = iw.synth_irq;
		/*
		 * iw.dma1_chan = 1; iw.dma2_chan = 3 ;
		 */
		hw->dma = iw.dma1_chan;
		hw->dma2 = iw.dma2_chan;


	}

	if (iw.csn > 0 && iw.csn < MAX_GUS_PNP) {
		gus_pnp_found[iw.csn] = hw->io_base;

	}
	iw.cdatap = iw.pcodar + 1;
	iw.csr1r = iw.pcodar + 2;
	iw.cxdr = iw.pcodar + 3;/* CPDR or CRDR */
	iw.gmxr = iw.p3xr;
	iw.gmxdr = iw.p3xr + 1;	/* GMTDR or GMRDR */
	iw.svsr = iw.p3xr + 2;
	iw.igidxr = iw.p3xr + 3;
	iw.i16dp = iw.p3xr + 4;
	iw.i8dp = iw.p3xr + 5;
	iw.lmbdr = iw.p3xr + 7;
	iw.voices = voices;

	if (iw.pnprdp > 0 && iw.csn > 0) {
		IwavePnpSetCfg();
		IwavePnpActivate(AUDIO, ON);
		IwavePnpActivate(EXT, ON);
	}
	/* IwavePnpActivate(EMULATION,ON); */


	/* reset */
	outb(iw.igidxr, _URSTI);/* Pull reset */
	outb(iw.i8dp, 0x00);
	DELAY(1000 * 30);

	outb(iw.i8dp, 0x01);	/* Release reset */
	DELAY(1000 * 30);

	/* end of reset */


	IwaveMemCfg(NULL);


	tmp = IwaveRegPeek(IDECI);

	IwaveRegPoke(IDECI, tmp | 0x18);

	IwaveCodecMode(CODEC_MODE2);	/* Default codec mode  */
	IwaveRegPoke(ICMPTI, 0);

	outb(iw.igidxr, 0x99);
	tmp = inb(iw.i8dp);
	outb(iw.igidxr, 0x19);
	outb(iw.i8dp, tmp);



	IwaveCodecIrq(~CODEC_IRQ_ENABLE);

	Iwaveinitcodec();

	outb(iw.p2xr, 0x0c);	/* Disable line in, mic and line out */

	IwaveRegPoke(CLCI, 0x3f << 2);

	IwaveLineLevel(0, _CLOAI);
	IwaveLineLevel(0, _CROAI);

	IwaveLineMute(OFF, _CLOAI);
	IwaveLineMute(OFF, _CROAI);

	IwaveLineLevel(0, _CLLICI);
	IwaveLineLevel(0, _CRLICI);
	IwaveLineMute(OFF, _CLLICI);
	IwaveLineMute(OFF, _CRLICI);

	IwaveLineLevel(0, _CLDACI);
	IwaveLineLevel(0, _CRDACI);
	IwaveLineMute(ON, _CLDACI);
	IwaveLineMute(ON, _CRDACI);

	IwaveLineLevel(0, _CLLICI);
	IwaveLineLevel(0, _CRLICI);
	IwaveLineMute(ON, _CLLICI);
	IwaveLineMute(ON, _CRLICI);


	IwaveInputSource(LEFT_SOURCE, MIC_IN);
	IwaveInputSource(RIGHT_SOURCE, MIC_IN);

	outb(iw.pcodar, 0x9 | 0x40);
	outb(iw.cdatap, 0);
	IwaveCodecIrq(CODEC_IRQ_ENABLE);
	outb(iw.pcodar, _CFIG3I | 0x20);


	outb(iw.cdatap, 0xC2);	/* Enable Mode 3 IRQs & Synth  */

	outb(iw.igidxr, _URSTI);
	outb(iw.i8dp, GF1_SET | GF1_OUT_ENABLE | GF1_IRQ_ENABLE);
	DELAY(1000 * 30);
	iw.size_mem = IwaveMemSize();	/* Bytes of RAM in this mode */
	outb(iw.p2xr, 0xc);	/* enable output */
	IwaveRegPoke(CLCI, 0x3f << 2);

	IwaveCodecIrq(CODEC_IRQ_ENABLE);
	splx(flags);

	DELAY(1000 * 100);
	IwaveRegPoke(CPDFI, 0);

	return (TRUE);
}


void
gus_wave_init(struct address_info * hw_config)
{
    u_long   flags;
    u_char   val, gus_pnp_seen = 0;
    char           *model_num = "2.4";
    int             gus_type = 0x24;	/* 2.4 */
    int irq = hw_config->irq, dma = hw_config->dma, dma2 = hw_config->dma2;
    int otherside = -1, i;

    if (irq < 0 || irq > 15) {
	printf("ERROR! Invalid IRQ#%d. GUS Disabled", irq);
	return;
    }
    if (dma < 0 || dma > 7) {
	printf("ERROR! Invalid DMA#%d. GUS Disabled", dma);
	return;
    }
    for (i = 0; i < MAX_GUS_PNP; i++) {
	if (gus_pnp_found[i] != 0 && gus_pnp_found[i] == hw_config->io_base)
	    gus_pnp_seen = 1;
    }
#ifdef NOGUSPNP
    gus_pnp_seen = 0;
#endif

    gus_irq = irq;
    gus_dma = dma;
    gus_dma2 = dma2;

    if (gus_dma2 == -1)
	gus_dma2 = dma;

    /*
     * Try to identify the GUS model.
     * 
     * Versions < 3.6 don't have the digital ASIC. Try to probe it first.
     */

    flags = splhigh();
    outb(gus_base + 0x0f, 0x20);
    val = inb(gus_base + 0x0f);
    splx(flags);

    if (val != 0xff && (val & 0x06)) {	/* Should be 0x02?? */
	/*
	 * It has the digital ASIC so the card is at least v3.4. Next
	 * try to detect the true model.
	 */

	val = inb(u_MixSelect);

	/*
	 * Value 255 means pre-3.7 which don't have mixer. Values 5
	 * thru 9 mean v3.7 which has a ICS2101 mixer. 10 and above
	 * is GUS MAX which has the CS4231 codec/mixer.
	 * 
	 */

	if (gus_pnp_seen)
	    val = 66;

	if (val == 255 || val < 5) {
	    model_num = "3.4";
	    gus_type = 0x34;
	} else if (val < 10) {
	    model_num = "3.7";
	    gus_type = 0x37;
	    mixer_type = ICS2101;
	} else {
	    if (gus_pnp_seen)
		model_num = "PNP";
	    else
		model_num = "MAX";

	    gus_type = 0x40;
	    mixer_type = CS4231;
#ifdef CONFIG_GUSMAX
	    {
		u_char   max_config = 0x40;	/* Codec enable */

		if (gus_dma2 == -1)
		    gus_dma2 = gus_dma;

		if (gus_dma > 3)
		    max_config |= 0x10;	/* 16 bit capture DMA */

		if (gus_dma2 > 3)
		    max_config |= 0x20;	/* 16 bit playback DMA */

		max_config |= (gus_base >> 4) & 0x0f;	/* Extract the X from
							 * 2X0 */

		outb(gus_base + 0x106, max_config);	/* UltraMax control */
	    }

	    if (ad1848_detect(gus_base + 0x10c, NULL, hw_config->osp)) {

		gus_mic_vol = gus_line_vol = gus_pcm_volume = 100;
		gus_wave_volume = 90;
		have_gus_max = 1;
		if (gus_pnp_seen) {

		    ad1848_init("GUS PNP", gus_base + 0x10c,
				-irq,
				gus_dma2,	/* Playback DMA */
				gus_dma,	/* Capture DMA */
				1,	/* Share DMA channels with GF1 */
				hw_config->osp);


		} else {
		    ad1848_init("GUS MAX", gus_base + 0x10c,
				-irq,
				gus_dma2,	/* Playback DMA */
				gus_dma,	/* Capture DMA */
				1,	/* Share DMA channels with GF1 */
				hw_config->osp);
		}
		otherside = num_audiodevs - 1;

	    } else
		printf("[Where's the CS4231?]");
#else
	    printf("\n\n\nGUS MAX support was not compiled in!!!\n\n\n\n");
#endif
	}
    } else {
	/*
	 * ASIC not detected so the card must be 2.2 or 2.4. There
	 * could still be the 16-bit/mixer daughter card.
	 */
    }

    if (gus_pnp_seen) {
	sprintf(gus_info.name, "Gravis %s (%dk)", model_num, (int) gus_mem_size / 1024);
    } else {
	sprintf(gus_info.name, "Gravis UltraSound %s (%dk)", model_num, (int) gus_mem_size / 1024);
    }
    conf_printf(gus_info.name, hw_config);

    if (num_synths >= MAX_SYNTH_DEV)
	printf("GUS Error: Too many synthesizers\n");
    else {
	voice_alloc = &guswave_operations.alloc;
	synth_devs[num_synths++] = &guswave_operations;
#ifdef CONFIG_SEQUENCER
	gus_tmr_install(gus_base + 8);
#endif
    }
    samples = (struct patch_info *) malloc((MAX_SAMPLE + 1) * sizeof(*samples), M_DEVBUF, M_NOWAIT);
    if (!samples)
	panic("SOUND: Cannot allocate memory\n");

    reset_sample_memory();

    gus_initialize();

    if (num_audiodevs < MAX_AUDIO_DEV) {
	audio_devs[gus_devnum = num_audiodevs++] = &gus_sampling_operations;
	audio_devs[gus_devnum]->otherside = otherside;
	audio_devs[gus_devnum]->dmachan1 = dma;
	audio_devs[gus_devnum]->dmachan2 = dma2;
	audio_devs[gus_devnum]->buffsize = DSP_BUFFSIZE;
	if (otherside != -1) {
	    /*
	     * glue logic to prevent people from opening the gus
	     * max via the gf1 and the cs4231 side . Only the gf1
	     * or the cs4231 are allowed to be open
	     */

	    audio_devs[otherside]->otherside = gus_devnum;
	}
	if (dma2 != dma && dma2 != -1)
	    audio_devs[gus_devnum]->flags |= DMA_DUPLEX;
    } else
	printf("GUS: Too many PCM devices available\n");

    /*
     * Mixer dependent initialization.
     */

    switch (mixer_type) {
    case ICS2101:
	gus_mic_vol = gus_line_vol = gus_pcm_volume = 100;
	gus_wave_volume = 90;
	ics2101_mixer_init();
	return;

    case CS4231:
	/* Initialized elsewhere (ad1848.c) */
    default:
	gus_default_mixer_init();
	return;
    }
}

static void
do_loop_irq(int voice)
{
	u_char   tmp;
	int             mode, parm;
	u_long   flags;

	flags = splhigh();
	gus_select_voice(voice);

	tmp = gus_read8(0x00);
	tmp &= ~0x20;		/* Disable wave IRQ for this_one voice */
	gus_write8(0x00, tmp);

	if (tmp & 0x03)		/* Voice stopped */
		voice_alloc->map[voice] = 0;

	mode = voices[voice].loop_irq_mode;
	voices[voice].loop_irq_mode = 0;
	parm = voices[voice].loop_irq_parm;

	switch (mode) {

	case LMODE_FINISH:	/* Final loop finished, shoot volume down */

		if ((int) (gus_read16(0x09) >> 4) < 100) {	/* Get current volume */
			gus_voice_off();
			gus_rampoff();
			gus_voice_init(voice);
			break;
		}
		gus_ramp_range(65, 4065);
		gus_ramp_rate(0, 63);	/* Fastest possible rate */
		gus_rampon(0x20 | 0x40);	/* Ramp down, once, irq */
		voices[voice].volume_irq_mode = VMODE_HALT;
		break;

	case LMODE_PCM_STOP:
		pcm_active = 0;	/* Signal to the play_next_pcm_block routine */
	case LMODE_PCM:
		{
			int             flag;	/* 0 or 2 */

			pcm_qlen--;
			pcm_head = (pcm_head + 1) % pcm_nblk;
			if (pcm_qlen && pcm_active) {
				play_next_pcm_block();
			} else {/* Underrun. Just stop the voice */
				gus_select_voice(0);	/* Left channel */
				gus_voice_off();
				gus_rampoff();
				gus_select_voice(1);	/* Right channel */
				gus_voice_off();
				gus_rampoff();
				pcm_active = 0;
			}

			/*
			 * If the queue was full before this interrupt, the
			 * DMA transfer was suspended. Let it continue now.
			 */
			if (dma_active) {
				if (pcm_qlen == 0)
					flag = 1;	/* Underflow */
				else
					flag = 0;
				dma_active = 0;
			} else
				flag = 2;	/* Just notify the dmabuf.c */
			DMAbuf_outputintr(gus_devnum, flag);
		}
		break;

	default:;
	}
	splx(flags);
}

static void
do_volume_irq(int voice)
{
	u_char   tmp;
	int             mode, parm;
	u_long   flags;

	flags = splhigh();

	gus_select_voice(voice);

	tmp = gus_read8(0x0d);
	tmp &= ~0x20;		/* Disable volume ramp IRQ */
	gus_write8(0x0d, tmp);

	mode = voices[voice].volume_irq_mode;
	voices[voice].volume_irq_mode = 0;
	parm = voices[voice].volume_irq_parm;

	switch (mode) {
	case VMODE_HALT:	/* Decay phase finished */
		splx(flags);
		gus_voice_init(voice);
		break;

	case VMODE_ENVELOPE:
		gus_rampoff();
		splx(flags);
		step_envelope(voice);
		break;

	case VMODE_START_NOTE:
		splx(flags);
		guswave_start_note2(voices[voice].dev_pending, voice,
		  voices[voice].note_pending, voices[voice].volume_pending);
		if (voices[voice].kill_pending)
			guswave_kill_note(voices[voice].dev_pending, voice,
					  voices[voice].note_pending, 0);

		if (voices[voice].sample_pending >= 0) {
			guswave_set_instr(voices[voice].dev_pending, voice,
					  voices[voice].sample_pending);
			voices[voice].sample_pending = -1;
		}
		break;

	default:;
	}
}

void
gus_voice_irq(void)
{
	u_long   wave_ignore = 0, volume_ignore = 0;
	u_long   voice_bit;

	u_char   src, voice;

	while (1) {
		src = gus_read8(0x0f);	/* Get source info */
		voice = src & 0x1f;
		src &= 0xc0;

		if (src == (0x80 | 0x40))
			return;	/* No interrupt */

		voice_bit = 1 << voice;

		if (!(src & 0x80))	/* Wave IRQ pending */
			if (!(wave_ignore & voice_bit) && (int) voice < nr_voices) {	/* Not done yet */
				wave_ignore |= voice_bit;
				do_loop_irq(voice);
			}
		if (!(src & 0x40))	/* Volume IRQ pending */
			if (!(volume_ignore & voice_bit) && (int) voice < nr_voices) {	/* Not done yet */
				volume_ignore |= voice_bit;
				do_volume_irq(voice);
			}
	}
}

void
guswave_dma_irq(void)
{
    u_char   status;

    status = gus_look8(0x41);	/* Get DMA IRQ Status */
    if (status & 0x40)	/* DMA interrupt pending */
	switch (active_device) {
	case GUS_DEV_WAVE:
	    if ((dram_sleep_flag.mode & WK_SLEEP)) {
		dram_sleep_flag.mode = WK_WAKEUP;
		wakeup(dram_sleeper);
	    };
	    break;

	case GUS_DEV_PCM_CONTINUE:	/* Left channel data transferred */
	    gus_transfer_output_block(pcm_current_dev, pcm_current_buf,
			  pcm_current_count, pcm_current_intrflag, 1);
	    break;

	case GUS_DEV_PCM_DONE:	/* Right or mono channel data transferred */
	    if (pcm_qlen < pcm_nblk) {
		int             flag = (1 - dma_active) * 2;	/* 0 or 2 */

		if (pcm_qlen == 0)
		    flag = 1;	/* Underrun */
		dma_active = 0;
		DMAbuf_outputintr(gus_devnum, flag);
	    }
	    break;

		default:;
		}

	status = gus_look8(0x49);	/* Get Sampling IRQ Status */
	if (status & 0x40) {	/* Sampling Irq pending */
		DMAbuf_inputintr(gus_devnum);
	}
}

#ifdef CONFIG_SEQUENCER
/*
 * Timer stuff
 */

static volatile int select_addr, data_addr;
static volatile int curr_timer = 0;

void
gus_timer_command(u_int addr, u_int val)
{
    int             i;

    outb(select_addr, (u_char) (addr & 0xff));

    for (i = 0; i < 2; i++)
	inb(select_addr);

    outb(data_addr, (u_char) (val & 0xff));

    for (i = 0; i < 2; i++)
	inb(select_addr);
}

static void
arm_timer(int timer, u_int interval)
{
    curr_timer = timer;

    if (timer == 1) {
	gus_write8(0x46, 256 - interval);	/* Set counter for timer 1 */
	gus_write8(0x45, 0x04);	/* Enable timer 1 IRQ */
	gus_timer_command(0x04, 0x01);	/* Start timer 1 */
    } else {
	gus_write8(0x47, 256 - interval);	/* Set counter for timer 2 */
	gus_write8(0x45, 0x08);	/* Enable timer 2 IRQ */
	gus_timer_command(0x04, 0x02);	/* Start timer 2 */
    }

    gus_timer_enabled = 0;
}

static u_int
gus_tmr_start(int dev, u_int usecs_per_tick)
{
    int             timer_no, resolution;
    int             divisor;

    if (usecs_per_tick > (256 * 80)) {
	timer_no = 2;
	resolution = 320;	/* usec */
    } else {
	timer_no = 1;
	resolution = 80;/* usec */
    }

    divisor = (usecs_per_tick + (resolution / 2)) / resolution;

    arm_timer(timer_no, divisor);

    return divisor * resolution;
}

static void
gus_tmr_disable(int dev)
{
    gus_write8(0x45, 0);	/* Disable both timers */
    gus_timer_enabled = 0;
}

static void
gus_tmr_restart(int dev)
{
    if (curr_timer == 1)
	gus_write8(0x45, 0x04);	/* Start timer 1 again */
    else
	gus_write8(0x45, 0x08);	/* Start timer 2 again */
}

static struct sound_lowlev_timer gus_tmr =
{
	0,
	gus_tmr_start,
	gus_tmr_disable,
	gus_tmr_restart
};

static void
gus_tmr_install(int io_base)
{
    select_addr = io_base;
    data_addr = io_base + 1;

    sound_timer_init(&gus_tmr, "GUS");
}
#endif
#endif
