/*
 *  Driver for Gravis UltraSound Extreme soundcards
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *
 *
 *   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"
#include "pcm.h"
#include "mixer.h"
#include "midi.h"
#include "gus.h"
#include "es1688.h"
#include "mpu401.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;	/* 0x220,0x240,0x260 */
int snd_mpu_port[ SND_CARDS ] = { [0 ... (SND_CARDS)-1] = -1 };
int snd_irq[ SND_CARDS ] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_mpu_irq[ SND_CARDS ] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_irq1[ SND_CARDS ] = SND_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
int snd_dma8[ SND_CARDS ] = SND_DEFAULT_DMA;	/* 0,1,3 */
int snd_dma8_size[ SND_CARDS ] = SND_DEFAULT_DMA_SIZE; /* 8,16,32,64,128 */
int snd_dma1[ SND_CARDS ] = SND_DEFAULT_DMA;
int snd_dma1_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) */
#ifdef MODULE_PARM
MODULE_PARM( snd_index, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_index, "Index value for GUS Extreme soundcard." );
MODULE_PARM( snd_id, "1-" __MODULE_STRING(SND_CARDS) "s" );
MODULE_PARM_DESC( snd_id, "ID string for GUS Extreme soundcard." );
MODULE_PARM( snd_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_mpu_port, "MPU-401 port # for GUS Extreme driver." );
MODULE_PARM( snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_port, "Port # for GUS Extreme driver." );
MODULE_PARM( snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_irq, "IRQ # for GUS Extreme driver." );
MODULE_PARM( snd_mpu_irq, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_mpu_irq, "MPU-401 IRQ # for GUS Extreme driver." );
MODULE_PARM( snd_irq1, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_irq1, "GF1 IRQ # for GUS Extreme driver." );
MODULE_PARM( snd_dma8, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma8, "8-bit DMA # for GUS Extreme driver." );
MODULE_PARM( snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma1, "GF1 DMA # for GUS Extreme 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 GUS Extreme driver." );
#endif

static struct snd_gusextreme {
  int irqnum;
  int mpuirqnum;
  int irq1num;
  int dma8num;
  int dma1num;
  snd_card_t *card;
  snd_gus_card_t *gus;
  snd_pcm_t *pcm;
  snd_pcm_t *pcm1;		/* GF1 PCM */
  snd_kmixer_t *mixer;
  snd_kmixer_t *mixer1;		/* GF1 mixer */
  snd_rawmidi_t *rmidi;
  unsigned short gus_status_reg;
} *snd_gusextreme_cards[ SND_CARDS ] = SND_DEFAULT_PTR;

static void snd_gusextreme_use_inc( snd_card_t *card )
{
  MOD_INC_USE_COUNT;
}

static void snd_gusextreme_use_dec( snd_card_t *card )
{
  MOD_DEC_USE_COUNT;
}

static snd_pcm_t *snd_gusextreme_detect( int dev,
                                         snd_card_t *card,
					 snd_gus_card_t *gus,
                                         unsigned short port,
                                         unsigned short mpu_port,
                                         unsigned short irqnum,
                                         unsigned short mpu_irqnum,
                                         unsigned short dma8num )
{
  snd_pcm_t *pcm;

  if ( snd_register_ioport( card, port, 16, "GUS Extreme - ES1688" ) < 0 )
    return NULL;
  if ( snd_register_ioport( card, port + 0x20, 16, "GUS Extreme (Adlib/SB compatibility)" ) < 0 ) { 
    snd_unregister_ioports( card ); 
    return NULL;
  }
  if ( snd_register_ioport( card, port + 0x120, 12, "GUS Extreme (synthesizer)" ) < 0 ) { 
    snd_unregister_ioports( card ); 
    return NULL;
  }
  if ( snd_register_ioport( card, port + 0x506, 1, "GUS Extreme (control)" ) < 0 ) { 
    snd_unregister_ioports( card ); 
    return NULL;
  }
  if ( mpu_irqnum != SND_IRQ_DISABLE ) {
    if ( snd_register_ioport( card, mpu_port, 2, "GUS Extreme - ES1688 - MPU-401" ) < 0 ) {
      snd_unregister_ioports( card );
      return NULL;
    }
  }
  pcm = snd_es1688_new_device( card, port, mpu_port, irqnum, mpu_irqnum, dma8num, ES1688_HW_1688_GUS );
  if ( !pcm ) {
    snd_unregister_ioports( card );
    return NULL;
  }

  snd_gus_set_port( gus, port + 0x20 );
  snd_gf1_i_write8( gus, SND_GF1_GB_RESET, 0 );	/* reset GF1 */
#ifdef SNDCFG_DEBUG_DETECT
  {
    unsigned char 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
  {
    unsigned char 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

  if ( snd_gus_detect_memory( gus ) < 0 ) goto __nodev;  
  if ( snd_gus_check_version( gus ) < 0 ) goto __nodev;
  if ( gus -> card -> type != SND_CARD_TYPE_GUS_EXTREME ) {
    snd_printdd( "GUS Extreme soundcard weren't detected at 0x%x\n", gus -> gf1.port );
    goto __nodev;
  }  
  
  return pcm;

  __nodev:
  snd_pcm_free( pcm );
  snd_unregister_ioports( card );
  return NULL;
}

static void snd_gusextreme_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  register int loop;
  register unsigned short status;
  struct snd_gusextreme *acard = (struct snd_gusextreme *)dev_id;

  if ( !acard || !acard -> gus ) return;

  do {
    loop = 0;
    if ( (status = inb( acard -> gus_status_reg )) ) {
      snd_gus_interrupt( acard -> gus, status );
      loop++;
    }
  } while ( loop );
}

static void snd_gusextreme_ess_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  struct snd_gusextreme *acard;
  
  acard = (struct snd_gusextreme *)dev_id;
  if ( acard == NULL || acard -> pcm == NULL ) return; 
  snd_es1688_interrupt( acard -> pcm );
}

static void snd_gusextreme_ess_midi_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  struct snd_gusextreme *acard;
  
  acard = (struct snd_gusextreme *)dev_id;
  if ( acard == NULL || acard -> rmidi == NULL ) return; 
  snd_mpu401_uart_interrupt( acard -> rmidi );
}

static int snd_gusextreme_resources( int dev, struct snd_gusextreme *acard, snd_card_t *card )
{
  static int possible_irqs[] = { 5, 9, 10, 7, -1 };
  static int possible_dmas[] = { 1, 3, 0, -1 };
  static int possible_irqs1[] = { 5, 11, 12, 9, 7, 15, 3, -1 };
  static int possible_dmas1[] = { 5, 6, 7, 1, 3, -1 };

  if ( (acard -> irqnum = snd_register_interrupt( card, "GUS Extreme - ES1688", snd_irq[ dev ], SND_IRQ_TYPE_ISA, snd_gusextreme_ess_interrupt, acard, possible_irqs )) < 0 )
    return acard -> irqnum;
  if ( (acard -> dma8num = snd_register_dma_channel( card, "GUS Extreme - ESx688", snd_dma8[ dev ], SND_DMA_TYPE_ISA, snd_dma8_size[ dev ], possible_dmas )) < 0 ) {
    return acard -> dma8num;
  }
  acard -> mpuirqnum = SND_IRQ_DISABLE;
  if ( snd_mpu_port[ dev ] >= 0x300 && snd_mpu_port[ dev ] <= 0x330 && (snd_mpu_port[ dev ] & 0x00f) == 0 ) {
    if ( (acard -> mpuirqnum = snd_register_interrupt( card, "GUS Extreme - ES1688 - MPU-401", snd_mpu_irq[ dev ], SND_IRQ_TYPE_ISA, snd_gusextreme_ess_midi_interrupt, acard, possible_irqs )) < 0 )
      acard -> mpuirqnum = SND_IRQ_DISABLE;
  }
  if ( (acard -> irq1num = snd_register_interrupt( card, "GUS Extreme - GF1", snd_irq1[ dev ], SND_IRQ_TYPE_ISA, snd_gusextreme_interrupt, acard, possible_irqs1 )) < 0 )
    return acard -> irqnum;
  if ( (acard -> dma1num = snd_register_dma_channel( card, "GF1 playback", snd_dma1[ dev ], SND_DMA_TYPE_ISA, snd_dma1_size[ dev ], possible_dmas1 )) < 0 ) {
    return acard -> dma1num;
  }
  return 0;
}

static void snd_gusextreme_init( int dev, snd_gus_card_t *gus )
{
  gus -> ess_flag = 1;
  gus -> joystick_dac = snd_joystick_dac[ dev ];
}

static int snd_gusextreme_mixer( snd_kmixer_t *mixer )
{
  snd_kmixer_channel_t *channel;
  
  /* 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 );
  return 0;
}

static int snd_gusextreme_probe( int dev, struct snd_gusextreme *acard )
{
  static int possible_ports[] = { 0x220,0x240,0x260,-1 };
  int *ports = possible_ports;
  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_kmixer_t *mixer1 = NULL;
  snd_rawmidi_t *rmidi = NULL;
  es1688_t *codec = NULL;

  card = snd_card_new( snd_index[ dev ], snd_id[ dev ],
  		       snd_gusextreme_use_inc, snd_gusextreme_use_dec );
  if ( !card ) return -ENOMEM;
  card -> type = SND_CARD_TYPE_ESS_ES1688;
  if ( snd_gusextreme_resources( dev, acard, card ) < 0 ) {
    snd_card_free( card );
    return -EBUSY;
  }
  gus = snd_gus_new_card( card,
  			  snd_port[ dev ],
  			  acard -> irq1num,
  			  acard -> dma1num,
  			  SND_DMA_DISABLE );
  if ( !gus ) {
    snd_card_free( card );
    return -ENOMEM;
  }
  if ( snd_port[ dev ] == SND_AUTO_PORT ) {
    for ( ports = possible_ports; *ports >= 0; ports++ ) {
      pcm = snd_gusextreme_detect( dev, card, gus, *ports, snd_mpu_port[ dev ], acard -> irqnum, acard -> mpuirqnum, acard -> dma8num );
      if ( pcm ) break;
    }
    if ( !pcm ) {
      snd_card_free( card );
      return -ENODEV;
    }
  } else {
    pcm = snd_gusextreme_detect( dev, card, gus, snd_port[ dev ], snd_mpu_port[ dev ], acard -> irqnum, acard -> mpuirqnum, acard -> dma8num );
    if ( !pcm ) {
      snd_card_free( card );
      return -ENODEV;
    }
  }
  codec = (es1688_t *)pcm -> private_data;
  acard -> gus_status_reg = gus -> gf1.reg_irqstat;
  snd_gusextreme_init( dev, gus );
  if ( snd_gus_init_dma_irq( gus, 1 ) < 0 ) {
    snd_pcm_free( pcm );
    snd_card_free( card );
    return -EINVAL;
  }            

  mixer = snd_es1688_new_mixer( pcm );
  if ( !mixer ) goto __nodev;
  if ( snd_gusextreme_mixer( mixer ) < 0 ) goto __nodev;
  if ( snd_es1688_init( pcm, 1 ) < 0 ) goto __nodev;
  
  pcm1 = snd_gf1_pcm_new_device( gus, mixer );
  if ( !pcm1 ) goto __nodev;
  mixer1 = snd_gf1_new_mixer( gus );
  if ( !mixer1 ) goto __nodev;        

#if 0
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_MASTER, 70, 70, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_FM, 75, 75, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_SYNTHESIZER, 80, 80, 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, 70, 70, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_MIC, 70, 70, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_CD, 70, 70, 0 );

  snd_mixer_channel_init( mixer1, SND_MIXER_PRI_MASTER, 100, 100, 0 );
  snd_mixer_channel_init( mixer1, SND_MIXER_PRI_LINE, 100, 100, 0 );
  snd_mixer_channel_init( mixer1, SND_MIXER_PRI_MIC, 100, 100, 0 );
#endif

  if ( acard -> mpuirqnum != SND_IRQ_DISABLE ) {
    rmidi = snd_mpu401_uart_new_device( card, MPU401_HW_ES1688, codec -> mpu_port, codec -> mpu_irq );
    if ( !rmidi ) goto __nodev;
    if ( snd_rawmidi_register( rmidi, 0 ) < 0 ) goto __nodev;
  }

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

  snd_enable_irq( card, acard -> irqnum );
  snd_enable_irq( card, acard -> irq1num );
  if ( acard -> mpuirqnum != SND_IRQ_DISABLE )
    snd_enable_irq( card, acard -> mpuirqnum );
  sprintf( card -> longname, "Gravis UltraSound Extreme at 0x%x, irq %i&%i, dma %i&%i",
  	codec -> port,
  	card -> irqs[ acard -> irqnum ] -> irq,
  	card -> irqs[ acard -> irq1num ] -> irq,
  	card -> dmas[ acard -> dma8num ] -> dma,
  	card -> dmas[ acard -> dma1num ] -> dma );
  snd_gf1_start( gus );
  if ( !snd_card_register( card ) ) {
    acard -> card = card;
    acard -> gus = gus;
    acard -> pcm = pcm;
    acard -> pcm1 = pcm1;
    acard -> mixer = mixer;
    acard -> mixer1 = mixer1;
    acard -> rmidi = rmidi;
    return 0;
  }
  snd_gf1_stop( gus );
  snd_pcm_unregister( pcm1 ); pcm1 = NULL;
  snd_pcm_unregister( pcm ); pcm = NULL;
  snd_mixer_unregister( mixer1 ); mixer1 = NULL;
  snd_mixer_unregister( mixer ); mixer = NULL;

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


int init_module( void )
{
  int dev, cards;
  struct snd_gusextreme *acard;

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

void cleanup_module( void )
{
  int idx;
  struct snd_gusextreme *acard;

  for ( idx = 0; idx < SND_CARDS; idx++ ) {
    acard = snd_gusextreme_cards[ idx ];
    if ( acard ) {
      snd_card_unregister( acard -> card );
      if ( acard -> gus ) {
        snd_gf1_stop( acard -> gus );
        snd_gus_init_dma_irq( acard -> gus, 0 );
      }
      if ( acard -> rmidi )
        snd_rawmidi_unregister( acard -> rmidi );
      if ( acard -> mixer1 )
        snd_mixer_unregister( acard -> mixer1 );
      if ( acard -> mixer )
        snd_mixer_unregister( acard -> mixer );
      if ( acard -> pcm1 ) {
        snd_pcm_unregister( acard -> pcm1 );
      }
      if ( acard -> pcm ) {
        snd_es1688_init( acard -> pcm, 0 );
        snd_pcm_unregister( acard -> pcm );
      }
      snd_card_free( acard -> card );
      snd_free( acard, sizeof( struct snd_gusextreme ) );
    }
  }
}
