/*
 *  Routines for Gravis UltraSound 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_MAIN_OBJECT_FILE
#include "driver.h"
#include "pcm.h"
#include "mixer.h"
#include "gus.h"

static void snd_gus_free_card( void *ptr )
{
  snd_gus_card_t *gus = (snd_gus_card_t *)ptr;
  snd_free( gus, sizeof( snd_gus_card_t ) );
}

snd_gus_card_t *snd_gus_new_card( snd_card_t *card,
                                  unsigned short port,
                                  unsigned short irqnum,
                                  unsigned short dma1num,
                                  unsigned short dma2num )
{
  snd_gus_card_t *gus;
  
  gus = (snd_gus_card_t *)snd_malloc( sizeof( snd_gus_card_t ) );
  if ( !gus ) return NULL;
  memset( gus, 0, sizeof( snd_gus_card_t ) );
  gus -> card = card;
  gus -> gf1.port = port;
  gus -> gf1.irqnum = irqnum;
  gus -> gf1.irq = card -> irqs[ irqnum ] -> irq;
  gus -> gf1.dma1num = dma1num;
  gus -> gf1.dma1 = card -> dmas[ dma1num ] -> dma;
  if ( dma2num != SND_DMA_DISABLE ) {
    gus -> gf1.dma2num = dma2num;
    gus -> gf1.dma2 = card -> dmas[ dma2num ] -> dma;
  } else {
    gus -> gf1.dma2num = gus -> gf1.dma1num;
    gus -> gf1.dma2 = gus -> gf1.dma1;
  }
  if ( gus -> gf1.dma1 == gus -> gf1.dma2 )
    gus -> equal_dma = 1;
  snd_spin_prepare( gus, reg );
  snd_spin_prepare( gus, active_voice );
  snd_spin_prepare( gus, playback );
  snd_spin_prepare( gus, dma );
  snd_spin_prepare( gus, pcm_volume_level );
  snd_spin_prepare( gus, uart_cmd );
  snd_spin_prepare( gus, neutral );
  snd_mutex_prepare( gus, dma );
  snd_sleep_prepare( gus, neutral );
  card -> private_data = gus;
  card -> private_free = snd_gus_free_card;
  return gus;
}

int snd_gus_set_port( snd_gus_card_t *gus, unsigned short port )
{
  gus -> gf1.port = port;
  /* fill register variables for speedup */
  gus -> gf1.reg_page = GUSP( gus, GF1PAGE );
  gus -> gf1.reg_regsel = GUSP( gus, GF1REGSEL );
  gus -> gf1.reg_data8 = GUSP( gus, GF1DATAHIGH );
  gus -> gf1.reg_data16 = GUSP( gus, GF1DATALOW );
  gus -> gf1.reg_irqstat = GUSP( gus, IRQSTAT );
  gus -> gf1.reg_dram = GUSP( gus, DRAM );
  gus -> gf1.reg_timerctrl = GUSP( gus, TIMERCNTRL );
  gus -> gf1.reg_timerdata = GUSP( gus, TIMERDATA );
  /* ---- */
  return 0;
}

/*
 *  Memory detection routine for plain GF1 soundcards
 */

int snd_gus_detect_memory( snd_gus_card_t *gus )
{
  int l, idx, local;
  unsigned char d;

  snd_gf1_poke( gus, 0L, 0xaa );
  snd_gf1_poke( gus, 1L, 0x55 );
  if ( snd_gf1_peek( gus, 0L ) != 0xaa || snd_gf1_peek( gus, 1L ) != 0x55 ) {
    snd_printk( "ultra: plain GF1 card at 0x%x without onboard DRAM?\n", gus -> gf1.port );
    return -ENOMEM;
  }
  for ( idx = 1, d = 0xaa; idx < 4; idx++, d++ ) {
    local = idx << 18;
    snd_gf1_poke( gus, local, d );
    snd_gf1_poke( gus, local + 1, d + 1 );
    if ( snd_gf1_peek( gus, local ) != d ||
         snd_gf1_peek( gus, local + 1 ) != d + 1 ||
         snd_gf1_peek( gus, 0L ) != 0xaa ) break;
  }
#if 1
  gus -> gf1.memory = idx << 18;
#else
  gus -> gf1.memory = 256 * 1024;
#endif
  for ( l = 0, local = gus -> gf1.memory; l < 4; l++, local -= 256 * 1024 ) {
    gus -> gf1.mem_alloc.banks_8[ l ].address =
    gus -> gf1.mem_alloc.banks_8[ l ].size = 0;
    gus -> gf1.mem_alloc.banks_16[ l ].address = l << 18;
    gus -> gf1.mem_alloc.banks_16[ l ].size = local > 0 ? 256 * 1024 : 0;
  }
  gus -> gf1.mem_alloc.banks_8[ 0 ].size = gus -> gf1.memory;
  return 0;		/* some memory were detected */
}

int snd_gus_init_dma_irq( snd_gus_card_t *gus, int latches )
{
  snd_card_t *card;
  unsigned long flags;
  unsigned char i, irq, dma;
  static unsigned char irqs[ 16 ] = { 0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7 };
  static unsigned char dmas[ 8 ] = { 6, 1, 0, 2, 0, 3, 4, 5 };

  if ( !gus ) return -EINVAL;
  card = gus -> card;
  if ( !card ) return -EINVAL;

  gus -> mix_cntrl_reg &= 0xf8;
  gus -> mix_cntrl_reg |= 0x01;	/* disable MIC, LINE IN, enable LINE OUT */
  if ( gus -> codec_flag || gus -> ess_flag ) {
    gus -> mix_cntrl_reg &= ~1;	/* enable LINE IN */
    gus -> mix_cntrl_reg |= 4;	/* enable MIC */
  }

  dma = dmas[ gus -> gf1.dma1 & 7 ];
  i = dmas[ gus -> gf1.dma2 & 7 ];
#if 0
  printk( "dma1 = %i, dma2 = %i\n", gus -> gf1.dma1, gus -> gf1.dma2 );
#endif
  if ( gus -> equal_dma || !i )
    {
      if ( !i )
        snd_printk( "GUS: Warning! DMA2 isn't defined.\n" );
      dma |= 0x40;
    }
   else
    dma |= i << 3;
    
  if ( ( dma & 7 ) == 0 )
    {
      snd_printk( "GUS: Error! DMA isn't defined.\n" );
      return -EINVAL;
    }

  irq = irqs[ gus -> gf1.irq & 0x0f ];
  if ( !irq )
    {
      snd_printk( "GUS: Error! IRQ isn't defined.\n" );
      return -EINVAL;
    }
  irq |= 0x40;
#if 0
  card -> mixer.mix_ctrl_reg |= 0x10;
#endif

  snd_spin_lock( gus, reg, &flags );
  outb( 5, GUSP( gus, REGCNTRLS ) );
  outb( gus -> mix_cntrl_reg, GUSP( gus, MIXCNTRLREG ) );
  outb( 0x00, GUSP( gus, IRQDMACNTRLREG ) );
  outb( 0, GUSP( gus, REGCNTRLS ) );
  snd_spin_unlock( gus, reg, &flags );

  snd_gf1_delay( gus );

  snd_spin_lock( gus, reg, &flags );
  outb( 0x00 | gus -> mix_cntrl_reg, GUSP( gus, MIXCNTRLREG ) );
  outb( dma, GUSP( gus, IRQDMACNTRLREG ) );
  if ( latches )
    {
      outb( 0x40 | gus -> mix_cntrl_reg, GUSP( gus, MIXCNTRLREG ) );
      outb( irq, GUSP( gus, IRQDMACNTRLREG ) );
    }
  snd_spin_unlock( gus, reg, &flags );

  snd_gf1_delay( gus );
  
  snd_spin_lock( gus, reg, &flags );
  outb( 0x00 | gus -> mix_cntrl_reg, GUSP( gus, MIXCNTRLREG ) );
  outb( dma, GUSP( gus, IRQDMACNTRLREG ) );
  if ( latches )
    {
      outb( 0x40 | gus -> mix_cntrl_reg, GUSP( gus, MIXCNTRLREG ) );
      outb( irq, GUSP( gus, IRQDMACNTRLREG ) );
    }
  snd_spin_unlock( gus, reg, &flags );

  snd_gf1_delay( gus );
  
  if ( latches )
    gus -> mix_cntrl_reg |= 0x08;	/* enable latches */
   else
    gus -> mix_cntrl_reg &= ~0x08;	/* disable latches */
  snd_spin_lock( gus, reg, &flags );
  outb( gus -> mix_cntrl_reg, GUSP( gus, MIXCNTRLREG ) );
  outb( 0, GUSP( gus, GF1PAGE ) );
  snd_spin_unlock( gus, reg, &flags );
  
  return 0; 
}

int snd_gus_check_version( snd_gus_card_t *gus )
{
  unsigned long flags;
  unsigned char val, rev;
  snd_card_t *card;

  card = gus -> card;
  snd_spin_lock( gus, reg, &flags );
  outb( 0x20, GUSP( gus, REGCNTRLS ) );
  val = inb( GUSP( gus, REGCNTRLS ) );
  rev = inb( GUSP( gus, BOARDVERSION ) );
  snd_spin_unlock( gus, reg, &flags );
  snd_printdd( "GF1 [0x%x] init - val = 0x%x, rev = 0x%x\n", gus -> gf1.port, val, rev );
  strcpy( card -> abbreviation, "GUS" );
  strcpy( card -> longname, "Gravis UltraSound Classic (2.4)" ); 
  card -> type = SND_CARD_TYPE_GUS_CLASSIC;
  if ( ( val != 255 && (val & 0x06) ) || ( rev >= 5 && rev != 255 ) ) {
    if ( rev >= 5 && rev <= 9 ) {
      gus -> ics_flag = 1;
      if ( rev == 5 ) gus -> ics_flipped = 1;
      card -> longname[ 27 ] = '3';
      card -> longname[ 29 ] = rev == 5 ? '5' : '7';
    }
    if ( rev >= 10 && rev != 255 ) {
      if ( rev >= 10 && rev <= 11 ) {
        strcpy( card -> longname, "Gravis UltraSound MAX" );
        card -> type = SND_CARD_TYPE_GUS_MAX;
      } else if ( rev == 0x30 ) {
        strcpy( card -> longname, "Gravis UltraSound Ace" );
        card -> type = SND_CARD_TYPE_GUS_ACE;
      } else if ( rev == 0x50 ) {
        strcpy( card -> longname, "Gravis UltraSound Extreme" );
        card -> type = SND_CARD_TYPE_GUS_EXTREME;
      } else {
        snd_printk( "unknown GF1 revision number at 0x%x - 0x%x (0x%x)\n", gus -> gf1.port, rev, val );
        snd_printk( "  please - report to <perex@jcu.cz>\n" );
      }
    }
  }
  strcpy( card -> shortname, card -> longname );
  gus -> uart_enable = 1;	/* standard GUSses doesn't have midi uart trouble */
  return 0;
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_gus_export;
#endif
 
int init_module( void )
{
#ifndef LINUX_2_1
  if ( register_symtab( &snd_symbol_table_gus_export ) < 0 )
    return -ENOMEM;
#endif
  return 0;
}

void cleanup_module( void )
{
}
