982e80577d
for possible buffer overflow problems. Replaced most sprintf()'s with snprintf(); for others cases, added terminating NUL bytes where appropriate, replaced constants like "16" with sizeof(), etc. These changes include several bug fixes, but most changes are for maintainability's sake. Any instance where it wasn't "immediately obvious" that a buffer overflow could not occur was made safer. Reviewed by: Bruce Evans <bde@zeta.org.au> Reviewed by: Matthew Dillon <dillon@apollo.backplane.com> Reviewed by: Mike Spengler <mks@networkcs.com>
4880 lines
122 KiB
C
4880 lines
122 KiB
C
/*
|
|
* 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
|