/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Detection routines for Sound Blaster cards
 *
 *
 *   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 "sb.h"

#define DSB( port, reg ) ( port + s_b_SB_##reg )

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

#if 0

static int snd_sb_dsp_get_byte( unsigned short port )
{
  int i;
  
  for ( i = 1000; i; i-- )
    if ( inb( DSB( port, DATA_AVAIL ) ) & 0x80 )
      return inb( DSB( port, READ ) );
  snd_printd( "sb get byte failed: 0x%x = 0x%x!!!\n", DSB( port, DATA_AVAIL ), inb( DSB( port, DATA_AVAIL ) ) );
  return -ENODEV;
}

#endif

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

  outb( 1, DSB( port, RESET ) );
  snd_delay( 1 );
  outb( 0, DSB( port, RESET ) );
  snd_delay( 3 );
  for ( i = 0; i < 1000 && !(inb( DSB( port, DATA_AVAIL ) ) & 0x80); i++ );
  if ( inb( DSB( port, READ ) ) != 0xaa ) {
#ifdef ULTRACFG_DEBUG
    snd_printk( "sb_reset: failed!!!\n" );
#endif
    return -ENODEV;
  }
  return 0;
}

int snd_detect_sb( snd_card_t *card, char *rname, unsigned int *rtype, unsigned short *rport )
{
  static short possible_ports[] = { 0x220, 0x240, 0x260, -1 };
  unsigned short major, minor, version;
  short *pport, port;
  int i;
  char str[ 12 ];

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

    if ( snd_register_ioport( card, port, 16, "Sound Blaster" ) < 0 )
      continue;
    
    /*
     *  initialization sequence
     */
  
    if ( snd_sb_reset( port ) < 0 ) {
      snd_printdd( "SB: [0x%x] reset failed... 0x%x\n", port, inb( DSB( port, READ ) ) );
      goto __nodev;
    }

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

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

    version = (major << 8) | minor;  
    if ( !version ) goto __nodev;		/* probably SB */
    switch ( major ) {
      case 1:
        strcpy( rname, "Sound Blaster 1.0" );
        *rtype = SND_CARD_TYPE_SB_10;
        break;
      case 2:
        strcpy( rname, minor > 1 ? "Sound Blaster 2.01+" : "Sound Blaster 2.0" );
        *rtype = SND_CARD_TYPE_SB_20;
        break;
      case 3:
        strcpy( rname, "Sound Blaster Pro" );
        *rtype = SND_CARD_TYPE_SB_PRO;
        break;
      case 4:
        strcpy( rname, "Sound Blaster 16" );
        *rtype = SND_CARD_TYPE_SB_16;
        break;
      default:
        snd_printk( "detect: Sound Blaster DSP chip found at 0x%x, but version 0x%x isn't known\n", port, version );
        goto __nodev;
    }      
    sprintf( str, " (%i.%i)", major, minor );
    strcat( rname, str );
    return 0;
       
    __nodev:
    snd_unregister_ioports( card );
  }

  return -ENODEV;
}
