/*
 *  Driver for Gravis UltraSound MAX soundcard
 *  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 "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;	/* 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;	/* 1,3,5,6,7 */
int snd_dma2[ SND_CARDS ] = SND_DEFAULT_DMA;	/* 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) */
#ifdef MODULE_PARM
MODULE_PARM( snd_index, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_index, "Index value for GUS MAX soundcard." );
MODULE_PARM( snd_id, "1-" __MODULE_STRING(SND_CARDS) "s" );
MODULE_PARM_DESC( snd_id, "ID string for GUS MAX soundcard." );
MODULE_PARM( snd_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_port, "Port # for GUS MAX driver." );
MODULE_PARM( snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_irq, "IRQ # for GUS MAX driver." );
MODULE_PARM( snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma1, "DMA1 # for GUS MAX driver." );
MODULE_PARM( snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma2, "DMA2 # for GUS MAX driver." );
MODULE_PARM( snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma1_size, "DMA1 size in kB for GUS MAX driver." );
MODULE_PARM( snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma2_size, "DMA2 size in kB for GUS MAX 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 MAX driver." );
#endif

struct snd_gusmax {
  int irqnum;
  int dma1num;
  int dma2num;
  snd_card_t *card;
  snd_gus_card_t *gus;
  snd_pcm_t *pcm, *pcm1;
  snd_kmixer_t *mixer;
  snd_rawmidi_t *midi_uart;
  unsigned short gus_status_reg;
  unsigned short pcm_status_reg;
};

static struct snd_gusmax *snd_gusmax_cards[ SND_CARDS ] = SND_DEFAULT_PTR;

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

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

static int snd_gusmax_detect( snd_gus_card_t *gus, unsigned short port )
{
  if ( snd_register_ioport( gus -> card, port, 16, "GUS MAX (Adlib/SB compatibility)" ) < 0 )
    return -EBUSY;
  if ( snd_register_ioport( gus -> card, port + 0x100, 12, "GUS MAX (synthesizer)" ) < 0 ) {
    snd_unregister_ioports( gus -> card );
    return -EBUSY;
  }
  if ( snd_register_ioport( gus -> card, port + 0x10c, 4, "GUS MAX (CS4231)" ) < 0 ) {
    snd_unregister_ioports( gus -> card );
    return -EBUSY;
  }
  if ( snd_register_ioport( gus -> card, port + 0x506, 1, "GUS MAX (control)" ) < 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
  {
    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_MAX ) {
    snd_printdd( "GUS MAX soundcard weren't detected at 0x%x\n", gus -> gf1.port );
    goto __nodev;
  }
  return 0;

  __nodev:
  snd_unregister_ioports( gus -> card );
  return -ENODEV;
}

static void snd_gusmax_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  register int loop;
  register unsigned short status;
  struct snd_gusmax *maxcard = (struct snd_gusmax *)dev_id;

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

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

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

  if ( (maxcard -> irqnum = snd_register_interrupt( card, "GUS MAX", snd_irq[ dev ], SND_IRQ_TYPE_ISA, snd_gusmax_interrupt, maxcard, possible_irqs )) < 0 ) {
    return maxcard -> irqnum;
  }
  if ( (maxcard -> dma1num = snd_register_dma_channel( card, "GF1/CS4231 record", snd_dma1[ dev ], SND_DMA_TYPE_ISA, snd_dma1_size[ dev ], possible_dmas )) < 0 ) {
    return maxcard -> dma1num;
  }
  if ( (snd_dma2[ dev ] >= 0 && snd_dma2[ dev ] <= 7) || snd_dma2[ dev ] == SND_AUTO_DMA ) {
    if ( (maxcard -> dma2num = snd_register_dma_channel( card, "CS4231 playback", snd_dma2[ dev ], SND_DMA_TYPE_ISA, snd_dma2_size[ dev ], possible_dmas )) < 0 ) {
      return maxcard -> dma2num;
    }
  } else {
    maxcard -> dma2num = SND_DMA_DISABLE;
  }
  return 0;
}

static void snd_gusmax_init( int dev, snd_card_t *card, snd_gus_card_t *gus )
{
  gus -> equal_irq = 1;
  gus -> codec_flag = 1;
  gus -> max_flag = 1;
  gus -> joystick_dac = snd_joystick_dac[ dev ];
  /* init control register */
  gus -> max_cntrl_val = ( gus -> gf1.port >> 4 ) & 0x0f;
  if ( gus -> gf1.dma1 > 3 ) gus -> max_cntrl_val |= 0x10;
  if ( gus -> gf1.dma2 > 3 ) gus -> max_cntrl_val |= 0x20;
  gus -> max_cntrl_val |= 0x40;
  outb( gus -> max_cntrl_val, GUSP( gus, MAXCNTRLPORT ) );
}

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

static int snd_gusmax_mixer( snd_kmixer_t *mixer )
{
  snd_kmixer_channel_t *channel1, *channel2;
  
  /* reassign AUXA to SYNTHESIZER */
  channel1 = snd_mixer_find_channel( mixer, SND_MIXER_PRI_AUXA );
  channel1 -> hw.priority = SND_MIXER_PRI_SYNTHESIZER;
  channel1 -> hw.ossdev = SND_MIXER_OSS_SYNTH;
  strcpy( channel1 -> hw.name, SND_MIXER_ID_SYNTHESIZER );
  snd_mixer_reorder_channel( mixer, channel1 );
  /* reassign AUXB to CD */
  channel2 = snd_mixer_find_channel( mixer, SND_MIXER_PRI_AUXB );
  channel2 -> hw.priority = SND_MIXER_PRI_CD;
  channel2 -> hw.ossdev = SND_MIXER_OSS_CD;
  channel2 -> hw.record = 1;
  channel2 -> hw.set_record_source = channel1 -> hw.set_record_source;
  strcpy( channel2 -> hw.name, SND_MIXER_ID_CD );
  snd_mixer_reorder_channel( mixer, channel2 );
  return 0;
}

static int snd_gusmax_probe( int dev, struct snd_gusmax *maxcard )
{
  static int possible_ports[] = { 0x220,0x230,0x240,0x250,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_rawmidi_t *midi_uart = NULL;
    
  card = snd_card_new( snd_index[ dev ], snd_id[ dev ],
                       snd_gusmax_use_inc, snd_gusmax_use_dec );
  if ( !card ) return -ENOMEM;
  if ( snd_gusmax_resources( dev, maxcard, card ) < 0 ) {
    snd_card_free( card );
    return -EBUSY;
  }
  gus = snd_gus_new_card( card,
                          snd_port[ dev ],
                          maxcard -> irqnum,
                          maxcard -> dma1num,
                          maxcard -> dma2num );
  if ( !gus ) {
    snd_card_free( card );
    return -ENOMEM;
  }
  if ( snd_port[ dev ] == SND_AUTO_PORT ) {
    for ( ports = possible_ports; *ports >= 0; ports++ ) {
      if ( !snd_gusmax_detect( gus, *ports ) ) break;
    }
    if ( *ports < 0 ) {
      goto __nodev;
    }
  } else {
    if ( snd_gusmax_detect( gus, snd_port[ dev ] ) ) {
      snd_card_free( card );
      return -ENODEV;
    }
  }
  maxcard -> gus_status_reg = gus -> gf1.reg_irqstat;
  maxcard -> pcm_status_reg = gus -> gf1.port + 0x10c + 2;
  snd_gusmax_init( dev, card, 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,
                               maxcard -> irqnum,
                               maxcard -> dma2num == SND_DMA_DISABLE ? maxcard -> dma1num : maxcard -> dma2num,
                               maxcard -> dma1num,
                               CS4231_HW_DETECT );
  if ( !pcm ) goto __nodev;
  mixer = snd_cs4231_new_mixer( pcm );
  if ( !mixer ) goto __nodev;
  pcm1 = snd_gf1_pcm_new_device( gus, mixer );
  if ( !pcm1 ) goto __nodev;
  if ( snd_gusmax_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_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 );
#endif

  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, maxcard -> irqnum );
#if 0
  snd_cs4231_debug( (cs4231_t *)pcm -> private_data );
#endif
  sprintf( card -> longname + strlen( card -> longname ), " at 0x%x, irq %i, dma %i",
    gus -> gf1.port,
    card -> irqs[ maxcard -> irqnum ] -> irq, 
    card -> dmas[ maxcard -> dma1num ] -> dma );
  if ( maxcard -> dma2num != SND_DMA_DISABLE )
    sprintf( card -> longname + strlen( card -> longname ), "&%i",
      card -> dmas[ maxcard -> dma2num ] -> dma );
  snd_gf1_start( gus );
  if ( !snd_card_register( card ) ) {
    maxcard -> card = card;
    maxcard -> gus = gus;
    maxcard -> pcm = pcm;
    maxcard -> pcm1 = pcm1;
    maxcard -> mixer = mixer;
    maxcard -> midi_uart = midi_uart;
    return 0;
  }
  snd_gf1_stop( gus );
  snd_mixer_unregister( mixer ); mixer = NULL;
  snd_pcm_unregister( pcm1 ); pcm1 = NULL;
  snd_pcm_unregister( pcm ); pcm = 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;
}

int init_module( void )
{
  int dev, cards;
  struct snd_gusmax *maxcard;

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

void cleanup_module( void )
{
  int idx;
  struct snd_gusmax *maxcard;
  snd_pcm_t *pcm;

  for ( idx = 0; idx < SND_CARDS; idx++ ) {
    maxcard = snd_gusmax_cards[ idx ];
    if ( maxcard ) {
      snd_card_unregister( maxcard -> card );
      if ( maxcard -> gus ) {
        snd_gf1_stop( maxcard -> gus );
        snd_gus_init_dma_irq( maxcard -> gus, 0 );
      }
      if ( maxcard -> midi_uart )
        snd_rawmidi_unregister( maxcard -> midi_uart );
      if ( maxcard -> mixer )
        snd_mixer_unregister( maxcard -> mixer );
      if ( maxcard -> pcm1 ) {
        snd_pcm_unregister( maxcard -> pcm1 );
      }
      if ( maxcard -> pcm ) {
        pcm = maxcard -> pcm;
        maxcard -> pcm = NULL;	/* turn off interrupts */
        snd_pcm_unregister( pcm );
      }
      snd_card_free( maxcard -> card );
      snd_free( maxcard, sizeof( struct snd_gusmax ) );
    }
  }
}
