/*
 *  Driver for AMD InterWave soundcard
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *
 *  Note: If you want use PnP driver with GUS PnP, you should have programmed
 *        onboard EEPROM with image from ftp://alsa.jcu.cz/pub/others/guspnp...
 *        
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#ifdef SNDCFG_PNP
#include <linux/pnp.h>
#endif
#include "pcm.h"
#include "mixer.h"
#include "midi.h"
#include "gus.h"
#include "cs4231.h"
#include "initval.h"

int snd_index[ SND_CARDS ] = SND_DEFAULT_IDX;	/* Index 1-MAX */
char *snd_id[ SND_CARDS ] = SND_DEFAULT_STR;	/* ID for this card */
int snd_port[ SND_CARDS ] = SND_DEFAULT_PORT;	/* 0x210,0x220,0x230,0x240,0x250,0x260 */
int snd_irq[ SND_CARDS ] = SND_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
int snd_dma1[ SND_CARDS ] = SND_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
int snd_dma2[ SND_CARDS ] = SND_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
int snd_dma1_size[ SND_CARDS ] = SND_DEFAULT_DMA_SIZE; /* 8,16,32,64,128 */
int snd_dma2_size[ SND_CARDS ] = SND_DEFAULT_DMA_SIZE; /* 8,16,32,64,128 */
int snd_joystick_dac[ SND_CARDS ] = { [0 ... (SND_CARDS-1)] = 29 };
				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
int snd_midi[ SND_CARDS ] = { [0 ... (SND_CARDS-1)] = 0 };
#ifdef MODULE_PARM
MODULE_PARM( snd_index, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_index, "Index value for InterWave soundcard." );
MODULE_PARM( snd_id, "1-" __MODULE_STRING(SND_CARDS) "s" );
MODULE_PARM_DESC( snd_id, "ID string for InterWave soundcard." );
#ifndef SNDCFG_PNP
MODULE_PARM( snd_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_port, "Port # for InterWave driver." );
MODULE_PARM( snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_irq, "IRQ # for InterWave driver." );
MODULE_PARM( snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma1, "DMA1 # for InterWave driver." );
MODULE_PARM( snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma2, "DMA2 # for InterWave driver." );
#endif
MODULE_PARM( snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma1_size, "DMA1 size in kB for InterWave driver." );
MODULE_PARM( snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma2_size, "DMA2 size in kB for InterWave driver." );
MODULE_PARM( snd_joystick_dac, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for InterWave driver." );
#endif

struct snd_interwave {
  int use_count;
  int irqnum;
  int dma1num;
  int dma2num;
  snd_card_t *card;
  snd_gus_card_t *gus;
  snd_pcm_t *pcm;
  snd_pcm_t *pcm1;		/* GF1 compatible PCM interface */
  snd_kmixer_t *mixer;
  snd_rawmidi_t *midi_uart;
  unsigned short gus_status_reg;
  unsigned short pcm_status_reg;
#ifdef SNDCFG_PNP
  struct pnp_device *pnpdev;
#endif
};

static struct snd_interwave *snd_interwave_cards[ SND_CARDS ] = SND_DEFAULT_PTR;

#ifdef SNDCFG_PNP
static struct pnp_driver snd_interwave_pnp;
#endif

static void snd_interwave_use_inc( snd_card_t *card )
{
  unsigned long flags;
  struct snd_interwave *iwcard;

  snd_cli( &flags );
  MOD_INC_USE_COUNT;
  iwcard = (struct snd_interwave *)card -> static_data;
  if ( ++iwcard -> use_count == 1 ) {
#ifdef SNDCFG_PNP
    if ( iwcard -> pnpdev ) {
      iwcard -> pnpdev -> flags |= PNP_DEV_LOCK;
    }
#endif
  }
  snd_sti( &flags );
}

static void snd_interwave_use_dec( snd_card_t *card )
{
  unsigned long flags;
  struct snd_interwave *iwcard;

  snd_cli( &flags );
  iwcard = (struct snd_interwave *)card -> static_data;
  if ( --iwcard -> use_count == 0 ) {
#ifdef SNDCFG_PNP
    if ( iwcard -> pnpdev ) {
      iwcard -> pnpdev -> flags &= ~PNP_DEV_LOCK;
    }
#endif
  }
  MOD_DEC_USE_COUNT;
  snd_sti( &flags );
}

static int snd_interwave_detect( snd_gus_card_t *gus, unsigned short port )
{
  unsigned long flags;
  unsigned char rev1, rev2;

  if ( snd_register_ioport( gus -> card, port, 16, "InterWave (Adlib/SB compatibility)" ) < 0 )
    return -EBUSY;
  if ( snd_register_ioport( gus -> card, port + 0x100, 12, "InterWave (synthesizer)" ) < 0 ) {
    snd_unregister_ioports( gus -> card );
    return -EBUSY;
  }
  if ( snd_register_ioport( gus -> card, port + 0x10c, 4, "InterWave (CS4231)" ) < 0 ) {
    snd_unregister_ioports( gus -> card );
    return -EBUSY;
  }
  snd_gus_set_port( gus, port );
  snd_gf1_i_write8( gus, SND_GF1_GB_RESET, 0 );	/* reset GF1 */
#ifdef SNDCFG_DEBUG_DETECT
  {
    int d;

    if ( ((d = snd_gf1_i_look8( gus, SND_GF1_GB_RESET )) & 0x07) != 0 ) {
      snd_printk( "[0x%x] check 1 failed - 0x%x\n", gus -> gf1.port, d );
      goto __nodev;
    }
  }
#else
  if ( (snd_gf1_i_look8( gus, SND_GF1_GB_RESET ) & 0x07) != 0 ) goto __nodev;
#endif
  snd_delay( 16 );
  snd_gf1_i_write8( gus, SND_GF1_GB_RESET, 1 );	/* release reset */
  snd_delay( 16 );
#ifdef SNDCFG_DEBUG_DETECT
  { 
    int d;

    if ( ((d = snd_gf1_i_look8( gus, SND_GF1_GB_RESET )) & 0x07) != 1 ) {
      snd_printk( "[0x%x] check 2 failed - 0x%x\n", gus -> gf1.port, d );
      goto __nodev;
    }
  }
#else
  if ( (snd_gf1_i_look8( gus, SND_GF1_GB_RESET ) & 0x07) != 1 ) goto __nodev;
#endif

  snd_spin_lock( gus, reg, &flags );
  rev1 = snd_gf1_look8( gus, SND_GF1_GB_VERSION_NUMBER );
  snd_gf1_write8( gus, SND_GF1_GB_VERSION_NUMBER, ~rev1 );
  rev2 = snd_gf1_look8( gus, SND_GF1_GB_VERSION_NUMBER );
  snd_gf1_write8( gus, SND_GF1_GB_VERSION_NUMBER, rev1 );
  snd_spin_unlock( gus, reg, &flags );
#ifdef SNDCFG_DEBUG_DETECT
  snd_printk( "[0x%x] InterWave check - rev1=0x%x, rev2=0x%x\n", gus -> gf1.port, rev1, rev2 );
#endif
  if ( ( rev1 & 0xf0 ) == ( rev2 & 0xf0 ) &&
       ( rev1 & 0x0f ) != ( rev2 & 0x0f ) )
    {
#ifdef SNDCFG_DEBUG_DETECT
      snd_printk( "[0x%x] InterWave check - passed\n", gus -> gf1.port );
#endif
      gus -> interwave = 1;
      strcpy( gus -> card -> shortname, "AMD InterWave" );
      gus -> revision = rev1 >> 4;
      return 0;			/* ok.. We have InterWave board */
    }
#ifdef SNDCFG_DEBUG_DETECT
  snd_printk( "[0x%x] InterWave check - failed\n", gus -> gf1.port );
#endif
  __nodev:
  snd_unregister_ioports( gus -> card );
  return -ENODEV;
}

static void snd_interwave_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  register int loop;
  register unsigned short status;
  struct snd_interwave *iwcard = (struct snd_interwave *)dev_id;

  if ( !iwcard || !iwcard -> gus || !iwcard -> pcm ) return;

  do {
    loop = 0;
    if ( (status = inb( iwcard -> gus_status_reg )) ) {
      snd_gus_interrupt( iwcard -> gus, status );
      loop++;
    }
    if ( (status = inb( iwcard -> pcm_status_reg )) & 0x01 ) {	/* IRQ bit is set? */
      snd_cs4231_interrupt( iwcard -> pcm, status );
      loop++;
    }
  } while ( loop );
}

static int snd_interwave_resources( int dev, struct snd_interwave *iwcard, snd_card_t *card )
{
  static int possible_irqs[] = { 5, 11, 12, 9, 7, 15, 3, -1 };
  static int possible_dmas[] = { 0, 1, 3, 5, 6, 7, -1 };

  if ( (iwcard -> irqnum = snd_register_interrupt( card, "InterWave", snd_irq[ dev ], SND_IRQ_TYPE_ISA, snd_interwave_interrupt, iwcard, possible_irqs )) < 0 ) {
    return iwcard -> irqnum;
  }
  if ( (iwcard -> dma1num = snd_register_dma_channel( card, "GFA1/CS4231 record", snd_dma1[ dev ], SND_DMA_TYPE_ISA, snd_dma1_size[ dev ], possible_dmas )) < 0 ) {
    return iwcard -> dma1num;
  }
  if ( snd_dma2[ dev ] >= 0 && snd_dma2[ dev ] <= 7 ) {
    if ( (iwcard -> dma2num = snd_register_dma_channel( card, "CS4231 playback", snd_dma2[ dev ], SND_DMA_TYPE_ISA, snd_dma2_size[ dev ], possible_dmas )) < 0 ) {
      return iwcard -> dma2num;
    }
  } else {
    iwcard -> dma2num = SND_DMA_DISABLE;
  }
  return 0;
}

static void snd_interwave_reset( snd_gus_card_t *gus )
{
  snd_gf1_write8( gus, SND_GF1_GB_RESET, 0x00 );
  snd_delay( 16 );
  snd_gf1_write8( gus, SND_GF1_GB_RESET, 0x01 );
  snd_delay( 16 );
}

static void snd_interwave_bank_sizes( snd_gus_card_t *gus, int *sizes )
{
  int idx;
  unsigned int local;
  unsigned char d;

  for ( idx = 0; idx < 4; idx++ )
    {
      sizes[ idx ] = 0;
      d = 0x55;
      for ( local = idx << 22;
            local < ( idx << 22 ) + 0x400000;
            local += 0x40000, d++ )
        {
          snd_gf1_poke( gus, local, d );
          snd_gf1_poke( gus, local + 1, d + 1 );
#if 0
          printk( "d = 0x%x, local = 0x%x, local + 1 = 0x%x, idx << 22 = 0x%x\n",
          		d,
                        snd_gf1_peek( gus, local ),
                        snd_gf1_peek( gus, local + 1 ),
                        snd_gf1_peek( gus, idx << 22 ) );
#endif
          if ( snd_gf1_peek( gus, local ) != d ||
               snd_gf1_peek( gus, local + 1 ) != d + 1 ||
               snd_gf1_peek( gus, idx << 22 ) != 0x55 ) break;
          sizes[ idx ]++;
        }
    }
#if 0
  printk( "sizes: %i %i %i %i\n", sizes[ 0 ], sizes[ 1 ], sizes[ 2 ], sizes[ 3 ] );
#endif
}

struct rom_hdr {
  /* 000 */ unsigned char iwave[ 8 ];
  /* 008 */ unsigned char rom_hdr_revision;
  /* 009 */ unsigned char series_number;
  /* 010 */ unsigned char series_name[ 16 ];
  /* 026 */ unsigned char date[ 10 ];
  /* 036 */ unsigned short vendor_revision_major;
  /* 038 */ unsigned short vendor_revision_minor;
  /* 040 */ unsigned int rom_size;
  /* 044 */ unsigned char copyright[ 128 ];
  /* 172 */ unsigned char vendor_name[ 64 ];
  /* 236 */ unsigned char rom_description[ 128 ];
  /* 364 */ unsigned char pad[ 147 ];
  /* 511 */ unsigned char csum;
};

static void snd_interwave_detect_memory( snd_gus_card_t *gus )
{
  static unsigned int lmc[ 13 ] = {
    0x00000001, 0x00000101, 0x01010101, 0x00000401,
    0x04040401, 0x00040101, 0x04040101, 0x00000004,
    0x00000404, 0x04040404, 0x00000010, 0x00001010,
    0x10101010
  };

  int i, bank_pos, pages;
  unsigned int lmct;
  int psizes[ 4 ];
  unsigned char csum;
  struct rom_hdr romh;

  gus -> card -> type = SND_CARD_TYPE_AMD_INTERWAVE;
  snd_interwave_reset( gus );
  snd_gf1_write8( gus, SND_GF1_GB_GLOBAL_MODE, snd_gf1_read8( gus, SND_GF1_GB_GLOBAL_MODE ) | 0x01 );	/* enhanced mode */
  snd_gf1_write8( gus, SND_GF1_GB_MEMORY_CONTROL, 0x01 );  /* DRAM I/O cycles selected */
  snd_gf1_write16( gus, SND_GF1_GW_MEMORY_CONFIG, ( snd_gf1_look16( gus, SND_GF1_GW_MEMORY_CONFIG ) & 0xff10 ) | 0x004c );
  /* ok.. simple test of memory size */
  pages = 0;
  snd_gf1_poke( gus, 0, 0x55 );
  snd_gf1_poke( gus, 1, 0xaa );
#if 1
  if ( snd_gf1_peek( gus, 0 ) == 0x55 && snd_gf1_peek( gus, 1 ) == 0xaa )
#else
  if ( 0 )		/* ok.. for testing of 0k RAM */
#endif
    {
      snd_interwave_bank_sizes( gus, psizes );
      lmct = ( psizes[ 3 ] << 24 ) | ( psizes[ 2 ] << 16 ) |
             ( psizes[ 1 ] << 8 ) | psizes[ 0 ];
#if 0
      printk( "lmct = 0x%08x\n", lmct );
#endif
      for ( i = 0; i < sizeof( lmc ) / sizeof( unsigned int ); i++ )
        if ( lmct == lmc[ i ] )
          {
#if 0
            printk( "found !!! %i\n", i );
#endif
            snd_gf1_write16( gus, SND_GF1_GW_MEMORY_CONFIG, ( snd_gf1_look16( gus, SND_GF1_GW_MEMORY_CONFIG ) & 0xfff0 ) | i );
            snd_interwave_bank_sizes( gus, psizes );
            break;
          }
      if ( i >= sizeof( lmc ) / sizeof( unsigned int ) && !gus -> gf1.enh_mode )
        snd_gf1_write16( gus, SND_GF1_GW_MEMORY_CONFIG, ( snd_gf1_look16( gus, SND_GF1_GW_MEMORY_CONFIG ) & 0xfff0 ) | 2 );
      for ( i = 0; i < 4; i++ )
        {
          gus -> gf1.mem_alloc.banks_8[ i ].address =
          gus -> gf1.mem_alloc.banks_16[ i ].address = i << 22;
          gus -> gf1.mem_alloc.banks_8[ i ].size =
          gus -> gf1.mem_alloc.banks_16[ i ].size = psizes[ i ] << 18;
          pages += psizes[ i ];
        }
    }
  pages <<= 18;
  gus -> gf1.memory = pages;
  
  snd_gf1_write8( gus, SND_GF1_GB_MEMORY_CONTROL, 0x03 );  /* select ROM */
  snd_gf1_write16( gus, SND_GF1_GW_MEMORY_CONFIG, ( snd_gf1_look16( gus, SND_GF1_GW_MEMORY_CONFIG ) & 0xff1f ) | ( 4 << 5 ) );
  gus -> gf1.rom_banks = 0;
  gus -> gf1.rom_memory = 0;
  for ( bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L )
    {
      for ( i = 0; i < sizeof( struct rom_hdr ); i++ )
        *(((unsigned char *)&romh) + i) = snd_gf1_peek( gus, i + bank_pos );
#ifdef SNDCFG_DEBUG_ROM
      printk( "ROM at 0x%06x = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", bank_pos,
        		romh.iwave[ 0 ], romh.iwave[ 1 ], romh.iwave[ 2 ], romh.iwave[ 3 ],
        		romh.iwave[ 4 ], romh.iwave[ 5 ], romh.iwave[ 6 ], romh.iwave[ 7 ] );
#endif
      if ( strncmp( romh.iwave, "INTRWAVE", 8 ) ) continue;  /* first check */
      csum = 0;
      for ( i = 0; i < sizeof( struct rom_hdr ) - 1; i++ )
        csum += *(((unsigned char *)&romh) + i); 
#ifdef SNDCFG_DEBUG_ROM
      printk( "ROM checksum = 0x%x == 0x%x (computed)\n", romh.csum, (unsigned char)(256 - csum) );
#endif
      if ( 256 - csum != romh.csum ) continue;		  /* not valid rom */
      gus -> gf1.rom_banks++;
      gus -> gf1.rom_present |= 1 << ( bank_pos >> 22 );
      gus -> gf1.rom_memory = snd_get_dword( (char *)&romh.rom_size, 0 );
    }
#if 0
  if ( gus -> gf1.rom_memory > 0 )
    {
      if ( gus -> gf1.rom_banks == 1 && gus -> gf1.rom_present == 8 )
        gus -> card -> type = SND_CARD_TYPE_IW_DYNASONIC;
    }
#endif
  snd_gf1_write8( gus, SND_GF1_GB_MEMORY_CONTROL, 0x00 );  /* select RAM */
    
  if ( !gus -> gf1.enh_mode ) snd_interwave_reset( gus );
}

static void snd_interwave_init( int dev, snd_gus_card_t *gus )
{
  unsigned long flags;

  /* ok.. some InterWave specific initialization */
  snd_spin_lock( gus, reg, &flags );
  snd_gf1_write8( gus, SND_GF1_GB_SOUND_BLASTER_CONTROL, 0x00 );
  snd_gf1_write8( gus, SND_GF1_GB_COMPATIBILITY, 0x1f );
  snd_gf1_write8( gus, SND_GF1_GB_DECODE_CONTROL, 0x49 );
  snd_gf1_write8( gus, SND_GF1_GB_VERSION_NUMBER, 0x11 );
  snd_gf1_write8( gus, SND_GF1_GB_MPU401_CONTROL_A, 0x00 );
  snd_gf1_write8( gus, SND_GF1_GB_MPU401_CONTROL_B, 0x30 );
  snd_gf1_write8( gus, SND_GF1_GB_EMULATION_IRQ, 0x00 );
  snd_spin_unlock( gus, reg, &flags );
  gus -> equal_irq = 1;
  gus -> codec_flag = 1;
  gus -> interwave = 1;
  gus -> max_flag = 1;
  gus -> joystick_dac = snd_joystick_dac[ dev ];
  
}

#define CS4231_PRIVATE( left, right, shift, mute ) ((left << 24)|(right << 16)|(shift<<8)|mute)

static int snd_interwave_mixer( snd_kmixer_t *mixer )
{
  static struct snd_stru_mixer_channel_hw master = {
    SND_MIXER_PRI_MASTER,		/* priority */
    SND_MIXER_PRI_PARENT,		/* parent priority */
    SND_MIXER_ID_MASTER,		/* device name */
    SND_MIXER_OSS_VOLUME,		/* OSS device # */
    1, 1, 1, 0,	0,			/* mute/stereo/record/digital */
    0, 31,				/* max. value */
    -3450, 1200, 150,			/* min, max, step - dB */
    CS4231_PRIVATE( CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 0, 0x80 ),
    NULL,				/* compute dB -> linear */
    NULL,				/* compute linear -> dB */
    NULL,				/* record source */
    NULL,				/* set mute */
    NULL,				/* set volume level */
  };
  snd_kmixer_channel_t *channel;
  int idx;
  
  /* ok. InterWave have MIC different (stereo) */
  channel = snd_mixer_find_channel( mixer, SND_MIXER_PRI_MIC );
  channel -> hw.stereo = 1;
  channel -> hw.max = 31;
  channel -> hw.private_value = CS4231_PRIVATE( CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 0, 0x80 );
  /* reassign AUXA to SYNTHESIZER */
  channel = snd_mixer_find_channel( mixer, SND_MIXER_PRI_AUXA );
  channel -> hw.priority = SND_MIXER_PRI_SYNTHESIZER;
  channel -> hw.ossdev = SND_MIXER_OSS_SYNTH;
  strcpy( channel -> hw.name, SND_MIXER_ID_SYNTHESIZER );
  snd_mixer_reorder_channel( mixer, channel );
  /* reassign AUXB to CD */  
  channel = snd_mixer_find_channel( mixer, SND_MIXER_PRI_AUXB );
  channel -> hw.priority = SND_MIXER_PRI_CD;
  channel -> hw.ossdev = SND_MIXER_OSS_CD;
  strcpy( channel -> hw.name, SND_MIXER_ID_CD );
  snd_mixer_reorder_channel( mixer, channel );
  /* make master as parent */
  for ( idx = 0; idx < mixer -> channels_count; idx++ ) {
    channel = mixer -> channels[ idx ];
    if ( !channel -> hw.input )
      channel -> hw.parent_priority = SND_MIXER_PRI_MASTER;
  }
  /* add master volume control */
  master.set_record_source = channel -> hw.set_record_source;
  master.set_mute = channel -> hw.set_mute;
  master.set_volume_level = channel -> hw.set_volume_level;
  channel = snd_mixer_new_channel( mixer, &master );
  if ( !channel ) return -ENOMEM;
  return 0;
}

static int snd_interwave_probe( int dev, struct snd_interwave *iwcard )
{
#ifndef SNDCFG_PNP
  static int possible_ports[] = { 0x210,0x220,0x230,0x240,0x250,0x260, -1 };
  int *ports = possible_ports;
#endif
  snd_card_t *card;
  snd_gus_card_t *gus = NULL;
  snd_pcm_t *pcm = NULL;
  snd_pcm_t *pcm1 = NULL;
  snd_kmixer_t *mixer = NULL;
  snd_rawmidi_t *midi_uart = NULL;
  char *str;
    
  card = snd_card_new( snd_index[ dev ], snd_id[ dev ],
                       snd_interwave_use_inc, snd_interwave_use_dec );
  if ( !card ) return -ENOMEM;
  card -> static_data = iwcard;
  if ( snd_interwave_resources( dev, iwcard, card ) < 0 ) {
    snd_card_free( card );
    return -EBUSY;
  }
  gus = snd_gus_new_card( card,
                          snd_port[ dev ],
                          iwcard -> irqnum,
                          iwcard -> dma1num,
                          iwcard -> dma2num );
  if ( !gus ) {
    snd_card_free( card );
    return -ENOMEM;
  }
#ifndef SNDCFG_PNP
  if ( snd_port[ dev ] == SND_AUTO_PORT ) {
    for ( ports = possible_ports; *ports >= 0; ports++ ) {
      if ( !snd_interwave_detect( gus, *ports ) ) break;
    }
    if ( *ports < 0 ) {
      snd_card_free( card );
      return -ENODEV;
    }
  } else {
#endif
    if ( snd_interwave_detect( gus, snd_port[ dev ] ) ) {
      snd_card_free( card );
      return -ENODEV;
    }
#ifndef SNDCFG_PNP
  }
#endif
  iwcard -> gus_status_reg = gus -> gf1.reg_irqstat;
  iwcard -> pcm_status_reg = gus -> gf1.port + 0x10c + 2;
  snd_interwave_init( dev, gus );
  snd_interwave_detect_memory( gus );
  if ( snd_gus_init_dma_irq( gus, 1 ) < 0 ) {
    snd_card_free( card );
    return -EINVAL;
  }
  pcm = snd_cs4231_new_device( card,
                               gus -> gf1.port + 0x10c,
                               iwcard -> irqnum,
                               iwcard -> dma2num == SND_DMA_DISABLE ? iwcard -> dma1num : iwcard -> dma2num,
                               iwcard -> dma1num,
                               CS4231_HW_INTERWAVE );
  if ( !pcm ) goto __nodev;
  sprintf( pcm -> name + strlen( pcm -> name ), " rev %c", gus -> revision + 'A' );
  strcat( pcm -> name, " (codec)" );
  mixer = snd_cs4231_new_mixer( pcm );
  if ( !mixer ) goto __nodev;
  pcm1 = snd_gf1_pcm_new_device( gus, mixer );
  if ( !pcm1 ) goto __nodev;
  if ( snd_interwave_mixer( mixer ) < 0 ) goto __nodev;

  snd_mixer_set_kernel_mute( mixer, SND_MIXER_PRI_PCM, SND_MIX_MUTE );
#if 0
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_MASTER, 70, 70, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_SYNTHESIZER, 75, 75, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_PCM, 85, 85, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_PCM1, 100, 100, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_LINE, 0, 0, SND_MIXER_FLG_MUTE );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_MIC, 0, 0, SND_MIXER_FLG_MUTE );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_CD, 0, 0, SND_MIXER_FLG_MUTE );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_GAIN, 0, 0, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_LOOPBACK, 0, 0, SND_MIXER_FLG_MUTE );
#endif

  gus -> uart_enable = snd_midi[ dev ];
  midi_uart = snd_gf1_rawmidi_new_device( gus );
  if ( !midi_uart ) goto __nodev;

  if ( snd_rawmidi_register( midi_uart, 0 ) < 0 ) goto __nodev;
  if ( snd_mixer_register( mixer, 0 ) < 0 ) {
    snd_rawmidi_unregister( midi_uart ); midi_uart = NULL;
    goto __nodev;
  }
  if ( snd_pcm_register( pcm, 0 ) < 0 ) {
    snd_rawmidi_unregister( midi_uart ); midi_uart = NULL;
    snd_mixer_unregister( mixer ); mixer = NULL;
    goto __nodev;
  }
  if ( snd_pcm_register( pcm1, 1 ) < 0 ) {
    snd_rawmidi_unregister( midi_uart ); midi_uart = NULL;
    snd_mixer_unregister( mixer ); mixer = NULL;
    snd_pcm_unregister( pcm ); pcm = NULL;
    goto __nodev;
  }

  snd_enable_irq( card, iwcard -> irqnum );
#if 0
  snd_cs4231_debug( (cs4231_t *)pcm -> private_data );
#endif
  strcpy( card -> abbreviation, "GUS" );
  strcpy( card -> shortname, "Gravis UltraSound" );
  str = "Gravis UltraSound Plug & Play";
  if ( gus -> gf1.rom_banks == 1 && gus -> gf1.rom_present == 8 ) {
    strcpy( card -> abbreviation, "Dynasonic" );
    strcpy( card -> shortname, "Dynasonic 3-D" );
    str = "Dynasonic 3-D";
  }
  sprintf( card -> longname, "%s at 0x%x, irq %i, dma %i",
    str,
    gus -> gf1.port,
    card -> irqs[ iwcard -> irqnum ] -> irq, 
    card -> dmas[ iwcard -> dma1num ] -> dma );
  if ( iwcard -> dma2num != SND_DMA_DISABLE )
    sprintf( card -> longname + strlen( card -> longname ), "&%i",
      card -> dmas[ iwcard -> dma2num ] -> dma );
  snd_gf1_start( gus );

  if ( !snd_card_register( card ) ) {
    iwcard -> card = card;
    iwcard -> gus = gus;
    iwcard -> pcm = pcm;
    iwcard -> pcm1 = pcm1;
    iwcard -> mixer = mixer;
    iwcard -> midi_uart = midi_uart;
    return 0;
  }
  snd_gf1_stop( gus );
  snd_pcm_unregister( pcm1 ); pcm1 = NULL;
  snd_pcm_unregister( pcm ); pcm = NULL;
  snd_mixer_unregister( mixer ); mixer = NULL;

  __nodev:
  snd_gus_init_dma_irq( gus, 0 );
  if ( midi_uart ) snd_rawmidi_free( midi_uart );
  if ( mixer ) snd_mixer_free( mixer );
  if ( pcm1 ) snd_pcm_free( pcm1 );
  if ( pcm ) snd_pcm_free( pcm );
  snd_card_free( card );
  return -ENXIO;
}

static int snd_interwave_free( int dev )
{
  struct snd_interwave *iwcard;
  snd_pcm_t *pcm;

  iwcard = snd_interwave_cards[ dev ];
  snd_interwave_cards[ dev ] = NULL;
  if ( iwcard ) {
    snd_card_unregister( iwcard -> card );
    if ( iwcard -> gus ) {
      snd_gf1_stop( iwcard -> gus );
      snd_gus_init_dma_irq( iwcard -> gus, 0 );
    }
    if ( iwcard -> midi_uart )
      snd_rawmidi_unregister( iwcard -> midi_uart );
    if ( iwcard -> mixer )
      snd_mixer_unregister( iwcard -> mixer );
    if ( iwcard -> pcm1 ) {
      snd_pcm_unregister( iwcard -> pcm1 );
    }
    if ( iwcard -> pcm ) {
      pcm = iwcard -> pcm;
      iwcard -> pcm = NULL;	/* turn off interrupts */
      snd_pcm_unregister( pcm );
    }
    snd_card_free( iwcard -> card );
    snd_free( iwcard, sizeof( struct snd_interwave ) );
  }
  return 0;
}

#ifdef SNDCFG_PNP
static struct snd_interwave *snd_interwave_pnp_card( struct pnp_device *d, int *rdev )
{
  int dev;
  struct snd_interwave *iwcard;

  if ( rdev ) *rdev = -1;
  for ( dev = 0; dev < SND_CARDS; dev++ ) {
    iwcard = snd_interwave_cards[ dev ];
    if ( !iwcard ) continue;
    if ( iwcard -> pnpdev != d ) continue;
    *rdev = dev;
    return iwcard;
  }
  return NULL;
}

static void snd_interwave_pnp_configure( int dev, struct pnp_resource *r )
{
  int nio = 0, ndma = 0;

  for ( ; r != NULL; r = r -> next ) {
    switch ( r -> type ) {
      case PNP_RES_IO:
        if ( nio == 0 ) snd_port[ dev ] = r -> start;
        nio++;
        break;
      case PNP_RES_DMA:
        if ( ndma == 0 ) snd_dma1[ dev ] = r -> start; else
        if ( ndma == 1 ) snd_dma2[ dev ] = r -> start;
        ndma++;
        break;
      case PNP_RES_IRQ:
        snd_irq[ dev ] = r -> start;
        break;
      default:
        break;
    }
  }
  if ( snd_dma2[ dev ] == snd_dma1[ dev ] )
    snd_dma2[ dev ] = -1;
}

static int snd_interwave_pnp_check( struct pnp_resource *r )
{
  int nio = 0, begin = 0;

  for ( ; r != NULL; r = r -> next ) {
    switch ( r -> type ) {
      case PNP_RES_IO:
#if 0
        printk( "nio = %i, start = 0x%lx\n", nio, r -> start );
#endif
        if ( nio == 0 ) begin = r -> start; else
        if ( nio == 1 && begin + 0x100 != r -> start ) return -EINVAL; else
        if ( nio == 2 && begin + 0x10c != r -> start ) return -EINVAL;
        nio++;
        break;
      default:
        break;
    }
  }
  return 0;
}

static int snd_interwave_pnp_event( struct pnp_device *d, struct pnp_drv_event *e )
{
  int dev = -1;
  struct snd_interwave *iwcard = NULL;

#if 0
  printk( "PNP event = %i, d = 0x%lx, use = %i\n", e -> type, (long)d, d -> in_use );  
#endif
  switch ( e -> type ) {
    case PNP_DRV_DISABLE:
    case PNP_DRV_EMERGSTOP:
      iwcard = snd_interwave_pnp_card( d, &dev );
      if ( !iwcard || dev < 0 ) return -ENODEV;
      return snd_interwave_free( dev );
    case PNP_DRV_ALLOC:
      for ( dev = 0; dev < SND_CARDS; dev++ )
        if ( snd_interwave_cards[ dev ] == NULL ) break;
      if ( dev >= SND_CARDS ) return -ENOMEM;
      iwcard = snd_malloc( sizeof( struct snd_interwave ) );
      if ( !iwcard ) return -ENOMEM;
      memset( iwcard, 0, sizeof( struct snd_interwave ) );
      snd_interwave_pnp_configure( dev, d -> res );
      if ( snd_interwave_probe( dev, iwcard ) < 0 ) {
        snd_printk( "Oops.. Fatal error.. GUS PnP soundcard can't be found with PnP method...\n" );
        snd_free( iwcard, sizeof( struct snd_interwave ) );
        return -ENXIO;
      }
      iwcard -> pnpdev = d;
      snd_interwave_cards[ dev ] = iwcard;
      return 0;
    case PNP_DRV_RECONFIG:
      return 0;
    case PNP_DRV_CONFIG:
      iwcard = snd_interwave_pnp_card( d, &dev );
      if ( !iwcard || dev < 0 ) return -ENODEV;
      if ( snd_interwave_pnp_check( d -> res ) < 0 ) return 0;
      snd_interwave_free( dev );
      iwcard = snd_malloc( sizeof( struct snd_interwave ) );
      if ( !iwcard ) {
        return -ENOMEM;
      }
      memset( iwcard, 0, sizeof( struct snd_interwave ) );
      snd_interwave_pnp_configure( dev, d -> res );
      if ( snd_interwave_probe( dev, iwcard ) < 0 ) {
        snd_printk( "Oops.. Fatal error.. GUS PnP soundcard can't be found with PnP method (reconfigure)...\n" );
        snd_free( iwcard, sizeof( struct snd_interwave ) );
        return -ENXIO;
      }
      iwcard -> pnpdev = d;
      snd_interwave_cards[ dev ] = iwcard;
      return 0;
  }
  return -EIO;
}

int init_module( void )
{
#ifndef LINUX_2_1
  register_symtab( NULL );
#endif 
  memset( &snd_interwave_pnp, 0, sizeof( snd_interwave_pnp ) );
  snd_interwave_pnp.id.type = PNP_HDL_ISA;
  snd_interwave_pnp.id.t.isa.id = PNP_EISAID( 'G', 'R', 'V', 0x0000 );
  snd_interwave_pnp.id.next = NULL;
  snd_interwave_pnp.event = snd_interwave_pnp_event;
  snd_interwave_pnp.name = "AMD InterWave";
  if ( pnp_register_driver( &snd_interwave_pnp, 1 ) < 0 ) {
    pnp_unregister_driver( &snd_interwave_pnp );
    return -ENOMEM;
  }
  return 0;
}

void cleanup_module( void )
{
  int err;
#ifndef SNDCFG_DEBUG
  struct snd_interwave *iwcard;

  for ( err = 0; err < SND_CARDS; err++ ) {
    iwcard = snd_interwave_cards[ err ];
    if ( !iwcard || !iwcard -> pnpdev ) continue;
    if ( iwcard -> pnpdev -> in_use ) {
      printk( "in use!!!" );
      iwcard -> pnpdev -> in_use = 0;
    }
  }
#endif
  if ( (err = pnp_unregister_driver( &snd_interwave_pnp )) < 0 )
    snd_printk( "gus-interwave.o: pnp_unregister_driver failure (%i)\n", err );
}

#endif

#ifndef SNDCFG_PNP

int init_module( void )
{
  int dev, cards;
  struct snd_interwave *iwcard;

#ifndef LINUX_2_1
  register_symtab( NULL );
#endif 
  for ( dev = cards = 0; dev < SND_CARDS && snd_port[ dev ] > 0; dev++ ) {
    iwcard = (struct snd_interwave *)snd_malloc( sizeof( struct snd_interwave ) );
    if ( !iwcard ) continue;
    memset( iwcard, 0, sizeof( struct snd_interwave ) );
    if ( snd_interwave_probe( dev, iwcard ) < 0 ) {
      snd_free( iwcard, sizeof( struct snd_interwave ) );
      if ( snd_port[ dev ] == SND_AUTO_PORT ) break;
      snd_printk( "InterWave soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[ dev ] );
      continue;
    }
    snd_interwave_cards[ dev ] = iwcard;
    cards++;
  }
  if ( !cards ) { 
    snd_printk( "InterWave soundcard #%i not found or device busy\n", dev + 1 );
    return -ENODEV;
  }
  return 0;
}

void cleanup_module( void )
{
  int dev;

  for ( dev = 0; dev < SND_CARDS; dev++ )
    snd_interwave_free( dev );
}

#endif
