/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Detection routines for ESS ESx688/488 chip
 *
 *
 *   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 "es1688.h"

#define DES1688P( port, reg ) ( port + e_s_s_ESS1688##reg )

extern int snd_detect_gus1( snd_card_t *card, char *rname, unsigned int *rtype, unsigned short port );

static int snd_es1688_dsp_command( unsigned short port, unsigned char val )
{
  int i;
  
  for ( i = 10000; i; i-- )
    if ( ( inb( DES1688P( port, STATUS ) ) & 0x80 ) == 0 ) {
      outb( val, DES1688P( port, COMMAND ) );
      return 1;
    }
#ifdef ULTRACFG_DEBUG
  printk( "snd_es1688_dsp_command: timeout (0x%x)\n", val );
#endif
  return 0;
}

#if 0

static int snd_es1688_dsp_get_byte( unsigned short port )
{
  int i;
  
  for ( i = 1000; i; i-- )
    if ( inb( DES1688P( port, DATA_AVAIL ) ) & 0x80 )
      return inb( DES1688P( port, READ ) );
  PRINTD( "ultra es1688 get byte failed: 0x%x = 0x%x!!!\n", DES1688P( port, DATA_AVAIL ), inb( DES1688P( port, DATA_AVAIL ) ) );
  return -ENODEV;
}

static int snd_es1688_write( unsigned short port, unsigned char reg, unsigned char data )
{
  if ( !snd_es1688_dsp_command( port, reg ) ) return 0;
  return snd_es1688_dsp_command( port, data );
}

static int snd_es1688_read( unsigned short port, unsigned char reg )
{
  /* Read a byte from an extended mode register of ES1688 */  
  if ( !snd_es1688_dsp_command( port, 0xc0 ) ) return -1;        
  if ( !snd_es1688_dsp_command( port, reg ) ) return -1;
  return snd_es1688_dsp_get_byte( port );
}

#endif

static void snd_es1688_mixer_write( unsigned short port, unsigned char reg, unsigned char data )
{
  unsigned long flags;
  
  snd_cli( &flags );
  outb( reg, DES1688P( port, MIXER_ADDR ) );
  snd_delay( 1 );
  outb( data, DES1688P( port, MIXER_DATA ) );
  snd_delay( 1 );
  snd_sti( &flags );
}

#if 0

static unsigned char snd_es1688_mixer_read( unsigned short port, unsigned char reg )
{
  unsigned long flags;
  unsigned char result;
  
  snd_cli( &flags );
  outb( reg, DES1688P( port, MIXER_ADDR ) );
  snd_delay( 1 );
  result = inb( DES1688P( port, MIXER_DATA ) );
  snd_delay( 1 );
  snd_sti( &flags );
  return result;
}

#endif

static int snd_es1688_reset( unsigned short port )
{
  int i;

  outb( 3, DES1688P( port, RESET ) );	/* valid only for ESS chips, SB -> 1 */
  snd_delay( 1 );
  outb( 0, DES1688P( port, RESET ) );
  snd_delay( 3 );
  for ( i = 0; i < 1000 && !(inb( DES1688P( port, DATA_AVAIL ) ) & 0x80); i++ );
  if ( inb( DES1688P( port, READ ) ) != 0xaa ) {
#ifdef ULTRACFG_DEBUG
    snd_printk( "ess_reset: failed!!!\n" );
#endif
    return -ENODEV;
  }
  snd_es1688_dsp_command( port, 0xc6 );	/* enable extended mode */
  return 0;
}

int snd_detect_es1688( snd_card_t *card, char *rname, unsigned int *rtype, unsigned short *rport )
{
  static short possible_ports[] = { 0x220, 0x240, 0x260, -1 };
  unsigned long flags;
  unsigned short major, minor, version;
  short *pport, port;
  int i, cfg1, cfg2, cfg3, gport;

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

    if ( snd_register_ioport( card, port, 16, "ESS AudioDrive" ) < 0 )
      continue;
    
    /*
     *  initialization sequence
     */
  
    snd_cli( &flags );                        /* Some ESS1688 cards need this */
    inb( DES1688P( port, ENABLE1 ) );
    inb( DES1688P( port, ENABLE1 ) );
    inb( DES1688P( port, ENABLE1 ) );
    inb( DES1688P( port, ENABLE2 ) );
    inb( DES1688P( port, ENABLE1 ) );
    inb( DES1688P( port, ENABLE2 ) );
    inb( DES1688P( port, ENABLE1 ) );
    inb( DES1688P( port, ENABLE1 ) );
    inb( DES1688P( port, ENABLE2 ) );
    inb( DES1688P( port, ENABLE1 ) );  
    inb( DES1688P( port, ENABLE0 ) );
    snd_sti( &flags );

    if ( snd_es1688_reset( port ) < 0 ) {
      snd_printdd( "ESS: [0x%x] reset failed... 0x%x\n", port, inb( DES1688P( port, READ ) ) );
      goto __nodev;
    }

    snd_es1688_dsp_command( port, 0xe7 ); 	/* return identification */
   
    for ( i = 1000, major = minor = 0; i; i-- ) {
      if ( inb( DES1688P( port, DATA_AVAIL ) ) & 0x80 )
        if ( major == 0 )
          major = inb( DES1688P( port, READ ) );
         else
          minor = inb( DES1688P( port, READ ) );
    }

    snd_printdd( "ESS: [0x%x] found.. major = 0x%x, minor = 0x%x\n", port, major, minor );

    version = (major << 8) | minor;  
    if ( !version ) goto __nodev;		/* probably SB */
    switch ( version & 0xfff0 ) {
      case 0x4880:
        strcpy( rname, "SoundBlaster Pro (ESS AudioDrive ES488)" );
        *rtype = SND_CARD_TYPE_SB_PRO;
        return 0;
      case 0x6880:
        goto __es1688;
      default:
        snd_printk( "detect: ESS AudioDrive chip found at 0x%x, but version 0x%x isn't known\n", port, version );
        goto __nodev;
    }      
  
    __es1688:

    /*
     *  check if we have GUS Extreme, don't try initialize other ESS cards
     */

    i = inb( DES1688P( port, INIT1 ) );
    snd_printdd( "ESS: [0x%x] (1) init1 = 0x%x\n", port, i );
    if ( !( i & 0x40 ) ) goto __plain_es1688;
    outb( 0, DES1688P( port, INIT1 ) );
    i = inb( DES1688P( port, INIT1 ) );
    snd_printdd( "ESS: [0x%x] (2) init1 = 0x%x\n", port, i );
    if ( i != 0x40 ) goto __plain_es1688;

    /*
     * This is main stuff - enable access to GF1 chip...
     * I'm not sure, if this will work for card which have
     * ES 1688 chip in another place than 0x220.
     *
     * ULTRINIT.EXE:
     * 0x230 = 0,2,3
     * 0x240 = 2,0,1
     * 0x250 = 2,0,3
     * 0x260 = 2,2,1
     */

    switch ( port ) {
      case 0x220:
      case 0x230:
        gport = 0x240;
        cfg1 = 2; cfg2 = 0; cfg3 = 1;
        break;
      case 0x240:
      case 0x250:
        gport = 0x260;
        cfg1 = 2; cfg2 = 2; cfg3 = 1;
        break;
      default:
        snd_printk( "detect: GUS Extreme: ESS chip found at unknown port 0x%x!!!", port );
        goto __nodev;
    }

    snd_es1688_mixer_write( port, 0x40, 0x8b );

    snd_cli( &flags );
    outb( cfg1, DES1688P( port, INIT1 ) );	/* 0x230 = 0 */
    outb( 0, 0x201 );
    outb( cfg2, DES1688P( port, INIT1 ) );	/* 0x230 = 2 */
    outb( 0, 0x201 );
    outb( cfg3, DES1688P( port, INIT1 ) );	/* 0x230,0x250 = 3 */
    snd_sti( &flags );

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

    snd_unregister_ioports( card );
    if ( snd_register_ioport( card, port, 16, "ESS AudioDrive" ) < 0 )
      continue;

    __plain_es1688:
    strcpy( rname, "ESS AudioDrive ES1688" );
    *rtype = SND_CARD_TYPE_ESS_ES1688;
    return 0;
     
    __nodev:
    snd_unregister_ioports( card );
  }

  return -ENODEV;
}
