diff options
Diffstat (limited to 'sys/i386/isa/sound/gus_wave.c')
-rw-r--r-- | sys/i386/isa/sound/gus_wave.c | 4879 |
1 files changed, 4879 insertions, 0 deletions
diff --git a/sys/i386/isa/sound/gus_wave.c b/sys/i386/isa/sound/gus_wave.c new file mode 100644 index 0000000..54d36cb --- /dev/null +++ b/sys/i386/isa/sound/gus_wave.c @@ -0,0 +1,4879 @@ +/* + * 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) { + snprintf(gus_info.name, sizeof(gus_info.name), + "Gravis %s (%dk)", model_num, (int) gus_mem_size / 1024); + } else { + snprintf(gus_info.name, sizeof(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 |