/*
 *  Detection routines for GF1 chip and for AMD InterWave (tm) soundcard
 *  Copyright (c) by Jaroslav Kysela (Perex soft)
 *
 *
 *   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.
 *
 */

#include "driver.h"
#include "pcm.h"
#include "mixer.h"
#include "gus.h"

#define DETECT_GUSP( port, x ) (port + SND_g_u_s_##x)

static void snd_d_gf1_write8( unsigned short port, unsigned char reg, unsigned char val )
{
  unsigned long flags;

  snd_cli( &flags );
  outb( reg, DETECT_GUSP( port, GF1REGSEL ) );
  outb( val, DETECT_GUSP( port, GF1DATAHIGH ) );
  snd_sti( &flags );
}

static unsigned char snd_d_gf1_look8( unsigned short port, unsigned char reg )
{
  unsigned long flags;
  unsigned char result;

  snd_cli( &flags );
  outb( reg, DETECT_GUSP( port, GF1REGSEL ) );
  result = inb( DETECT_GUSP( port, GF1DATAHIGH ) );
  snd_sti( &flags );
  return result;
}

static void snd_d_gf1_poke( unsigned short port, unsigned long addr, unsigned char value )
{
  unsigned long flags;

  snd_cli( &flags );
  outb( 0x43, DETECT_GUSP( port, GF1REGSEL ) );
  outw( (unsigned short)addr, DETECT_GUSP( port, GF1DATALOW ) );
  outb( 0x44, DETECT_GUSP( port, GF1REGSEL ) );
  outb( (unsigned char)(addr >> 16), DETECT_GUSP( port, GF1DATAHIGH ) );
  outb( value, DETECT_GUSP( port, DRAM ) );
  snd_sti( &flags );                
}

static unsigned char snd_d_gf1_peek( unsigned short port, unsigned long addr )
{
  unsigned long flags;
  unsigned char result;

  snd_cli( &flags );
  outb( 0x43, DETECT_GUSP( port, GF1REGSEL ) );
  outw( (unsigned short)addr, DETECT_GUSP( port, GF1DATALOW ) );
  outb( 0x44, DETECT_GUSP( port, GF1REGSEL ) );
  outb( (unsigned char)(addr >> 16), DETECT_GUSP( port, GF1DATAHIGH ) );
  result = inb( DETECT_GUSP( port, DRAM ) );
  snd_sti( &flags );                
  return result;
}

int snd_detect_interwave( snd_card_t *card, char *rname, unsigned int *rtype, unsigned short *rport )
{
  static short possible_ports[] = { 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, -1 };
  unsigned long flags;
  unsigned char tmp, rev1, rev2;
  short port, *pport;

  for ( pport = possible_ports; *pport > 0; pport++ ) {
    port = *pport;

    if ( snd_register_ioport( card, port, 16, "InterWave (Adlib/SB compatibility)" ) < 0 )
      continue;
    if ( snd_register_ioport( card, port + 0x100, 12, "InterWave (synthesizer)" ) < 0 ) {
      snd_unregister_ioports( card );
      continue;
    }
    if ( snd_register_ioport( card, port + 0x10c, 4, "InterWave (CS4231)" ) < 0 ) {
      snd_unregister_ioports( card );
      continue;
    }
#if 0
    snd_printk( "Probing InterWave at 0x%x\n", port );
#endif

    /*
     * ok.. use only INB at first stage of detection to avoid
     * trouble with wrong outb(..
     */

    tmp = inb( DETECT_GUSP( port, MIXCNTRLREG ) );
    snd_printdd( "interwave [0x%x]: MIXCNTRLREG = 0x%x\n", port, tmp );
    if ( tmp & 0x80 ) goto __nodev;
    tmp = inb( DETECT_GUSP( port, REGCNTRLS ) );
    snd_printdd( "interwave [0x%x]: REGCNTRLS = 0x%x\n", port, tmp );
    if ( tmp & 0x04 ) goto __nodev;
    tmp = inb( DETECT_GUSP( port, GF1PAGE ) );
    snd_printdd( "interwave [0x%x]: GF1PAGE = 0x%x\n", port, tmp );
    if ( tmp & 0x60 ) goto __nodev;
  
    snd_d_gf1_write8( port, SND_GF1_GB_RESET, 0 );	/* reset GF1 */
    tmp = snd_d_gf1_look8( port, SND_GF1_GB_RESET );
    if ( (tmp & 0x07) != 0 ) goto __nodev;
    snd_delay( 16 );
    snd_d_gf1_write8( port, SND_GF1_GB_RESET, 1 );	/* release reset */
    snd_delay( 16 );
    tmp = snd_d_gf1_look8( port, SND_GF1_GB_RESET );
    if ( (tmp & 0x07) != 1 ) goto __nodev;

    snd_cli( &flags );
    rev1 = snd_d_gf1_look8( port, SND_GF1_GB_VERSION_NUMBER );
    snd_d_gf1_write8( port, SND_GF1_GB_VERSION_NUMBER, ~rev1 );
    rev2 = snd_d_gf1_look8( port, SND_GF1_GB_VERSION_NUMBER );
    snd_d_gf1_write8( port, SND_GF1_GB_VERSION_NUMBER, rev1 );
    snd_sti( &flags );

    if ( ( rev1 & 0xf0 ) == ( rev2 & 0xf0 ) &&
         ( rev1 & 0x0f ) != ( rev2 & 0x0f ) ) {
      strcpy( rname, "AMD InterWave" );
      *rtype = SND_CARD_TYPE_AMD_INTERWAVE;
      *rport = port;
      return 0;		/* ok.. We have InterWave board */
    }

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

int snd_detect_gus1( snd_card_t *card, char *rname, unsigned int *rtype, unsigned short port )
{
  unsigned long flags;
  unsigned char tmp, val, rev;

  if ( snd_register_ioport( card, port, 16, "GF1 (Adlib/SB compatibility)" ) < 0 )
    return -ENODEV;
  if ( snd_register_ioport( card, port + 0x100, 12, "GF1 (synthesizer)" ) < 0 )
    return -ENODEV;
  if ( snd_register_ioport( card, port + 0x506, 1, "GUS (control)" ) < 0 )
    return -ENODEV;

  /*
   * ok.. use only INB at first stage of detection to avoid
   * trouble with wrong outb(..
   */

  tmp = inb( DETECT_GUSP( port, REGCNTRLS ) );
  snd_printdd( "gus [0x%x]: REGCNTRLS = 0x%x\n", port, tmp );
  if ( tmp & 0x80 ) return -ENODEV;
#if 0
  tmp = inb( DETECT_GUSP( port, MIDISTAT ) );
  snd_printdd( "gus [0x%x]: MIDISTAT = 0x%x\n", port, tmp );
  if ( tmp & 0x4c ) return -ENODEV;
#endif
  
  snd_d_gf1_write8( port, SND_GF1_GB_RESET, 0 );	/* reset GF1 */
  tmp = snd_d_gf1_look8( port, SND_GF1_GB_RESET );
  if ( (tmp & 0x07) != 0 ) return -ENODEV;
  snd_delay( 16 );
  snd_d_gf1_write8( port, SND_GF1_GB_RESET, 1 );	/* release reset */
  snd_delay( 16 );
  tmp = snd_d_gf1_look8( port, SND_GF1_GB_RESET );
  if ( (tmp & 0x07) != 1 ) return -ENODEV;

  snd_d_gf1_poke( port, 0L, 0xaa );
  snd_d_gf1_poke( port, 1L, 0x55 );
  if ( snd_d_gf1_peek( port, 0L ) != 0xaa || snd_d_gf1_peek( port, 1L ) != 0x55 )
    return -ENODEV;

  snd_cli( &flags );
  outb( 0x20, DETECT_GUSP( port, REGCNTRLS ) );
  val = inb( DETECT_GUSP( port, REGCNTRLS ) );
  rev = inb( DETECT_GUSP( port, BOARDVERSION ) );
  snd_sti( &flags );
  snd_printdd( "GF1 [0x%x] init - val = 0x%x, rev = 0x%x\n", port, val, rev );
  strcpy( rname, "Gravis UltraSound Classic (2.4)" );
  *rtype = SND_CARD_TYPE_GUS_CLASSIC;
  if ( ( val != 255 && (val & 0x06) ) || ( rev >= 5 && rev != 255 ) ) {
    if ( rev >= 5 && rev <= 9 ) {
      rname[ 27 ] = '3';
      rname[ 29 ] = rev == 5 ? '5' : '7';
    }
    if ( rev >= 10 && rev != 255 ) {
      if ( rev >= 10 && rev <= 11 ) {
        strcpy( rname, "Gravis UltraSound MAX" );
        *rtype = SND_CARD_TYPE_GUS_MAX;
      } else if ( rev == 0x30 ) {
        strcpy( rname, "Gravis UltraSound Ace" );
        *rtype = SND_CARD_TYPE_GUS_ACE;
      } else if ( rev == 0x50 ) {
        strcpy( rname, "Gravis UltraSound Extreme" );
        *rtype = SND_CARD_TYPE_GUS_EXTREME;
      } else {
        snd_printk( "unknown GF1 revision number at 0x%x - 0x%x (0x%x)\n", port, rev, val );
        snd_printk( "  please - report to <perex@jcu.cz>\n" );
      }
    }
  }
  return 0;
}

int snd_detect_gus( snd_card_t *card, char *rname, unsigned int *rtype, unsigned short *rport )
{
  static short possible_ports[] = { 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, -1 };
  short port, *pport;

  for ( pport = possible_ports; *pport > 0; pport++ ) {
    port = *rport = *pport;

    if ( !snd_detect_gus1( card, rname, rtype, port ) )
      return 0;

    snd_unregister_ioports( card );
  }
  return -ENODEV;
}
