/*
 *  Driver for SoundBlaster 16/AWE32/AWE64 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"
#ifdef SNDCFG_PNP
#include <linux/pnp.h>
#endif
#include "pcm.h"
#include "mixer.h"
#include "midi.h"
#include "sb.h"
#include "mpu401.h"
#include "initval.h"

#if 0
#define SND_TEST_SB_OLD_MIDI	/* doesn't work for me (SB AWE 64) - Perex */
#endif

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_PNP_PORT; /* 0x220,0x240,0x260 */
int snd_mpu_port[ SND_CARDS ] = { 0x330, 0x300, [2 ... (SND_CARDS-1)] = -1 };
int snd_irq[ SND_CARDS ] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_dma8[ SND_CARDS ] = SND_DEFAULT_DMA;	/* 0,1,3 */
int snd_dma8_size[ SND_CARDS ] = SND_DEFAULT_DMA_SIZE; /* 8,16,32,64,128 */
int snd_dma16[ SND_CARDS ] = SND_DEFAULT_DMA;	/* 5,6,7 */
int snd_dma16_size[ SND_CARDS ] = SND_DEFAULT_DMA_SIZE; /* 8,16,32,64,128 */
int snd_mic_agc[ SND_CARDS ] = { [0 ... (SND_CARDS-1)] = 1 };
#ifdef MODULE_PARM
MODULE_PARM( snd_index, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_index, "Index value for SoundBlaster 16 soundcard." );
MODULE_PARM( snd_id, "1-" __MODULE_STRING(SND_CARDS) "s" );
MODULE_PARM_DESC( snd_id, "ID string for SoundBlaster 16 soundcard." );
#ifndef SNDCFG_PNP
MODULE_PARM( snd_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_port, "Port # for SB16 driver." );
MODULE_PARM( snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_mpu_port, "MPU-401 port # for SB16 driver." );
MODULE_PARM( snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_irq, "IRQ # for SB16 driver." );
MODULE_PARM( snd_dma8, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma8, "8-bit DMA # for SB16 driver." );
MODULE_PARM( snd_dma16, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma16, "16-bit DMA # for SB16 driver." );
#endif
MODULE_PARM( snd_dma8_size, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma8_size, "Size of 8-bit DMA # for SB16 driver." );
MODULE_PARM( snd_dma16_size, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma16_size, "Size of 16-bit DMA # for SB16 driver." );
MODULE_PARM( snd_mic_agc, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_mic_agc, "Mic Auto-Gain-Control switch." );
#endif

static struct snd_sb16 {
  int use_count;
  int irqnum;
  int dma8num;
  int dma16num;
  snd_card_t *card;
  snd_pcm_t *pcm;
  snd_kmixer_t *mixer;
  snd_rawmidi_t *rmidi;
#ifdef SNDCFG_PNP
  struct pnp_device *pnpdev;
#endif
} *snd_sb16_cards[ SND_CARDS ] = SND_DEFAULT_PTR;

#ifdef SNDCFG_PNP
static struct pnp_driver snd_sb16_pnp;
static pnp_dev_t snd_sb16_pnp_awe64_id;
#endif

static void snd_sb16_use_inc( snd_card_t *card )
{
  unsigned long flags;
  struct snd_sb16 *acard;

  snd_cli( &flags );
  MOD_INC_USE_COUNT;
  acard = (struct snd_sb16 *)card -> static_data;
  if ( ++acard -> use_count == 1 ) {
#ifdef SNDCFG_PNP
    if ( acard -> pnpdev ) {
      acard -> pnpdev -> flags |= PNP_DEV_LOCK;
    }
#endif
  }
  snd_sti( &flags );
}

static void snd_sb16_use_dec( snd_card_t *card )
{
  unsigned long flags;
  struct snd_sb16 *acard;

  snd_cli( &flags );
  acard = (struct snd_sb16 *)card -> static_data;
  if ( --acard -> use_count == 0 ) {
#ifdef SNDCFG_PNP
    if ( acard -> pnpdev ) {
      acard -> pnpdev -> flags &= ~PNP_DEV_LOCK;
    }
#endif
  }
  MOD_DEC_USE_COUNT;
  snd_sti( &flags );
}

static snd_pcm_t *snd_sb16_detect( int dev,
                                   snd_card_t *card,
                                   unsigned short port,
                                   unsigned short irqnum,
                                   unsigned short dma8num,
                                   unsigned short dma16num )
{
  snd_pcm_t *pcm;
  sbdsp_t *codec;
  int mpu_flag = 0;

  if ( snd_register_ioport( card, port, 16, "Sound Blaster 16" ) < 0 )
    return NULL;
  if ( snd_mpu_port[ dev ] == 0x300 || snd_mpu_port[ dev ] == 0x330 ) {
    if ( snd_register_ioport( card, snd_mpu_port[ dev ], 2, "Sound Blaster 16 - MPU-401" ) < 0 ) {
      snd_printk( "sb16: port 0x%x isn't free for MPU-401, midi port is disabled\n", snd_mpu_port[ dev ] );
    } else {
      mpu_flag = 1;
    }
  }
  pcm = snd_sbdsp_new_device( card, port, irqnum, dma8num, dma16num, SB_HW_16 );
  if ( !pcm ) {
    snd_unregister_ioports( card );
    return NULL;
  }
  codec = (sbdsp_t *)pcm -> private_data;
  if ( codec -> hardware != SB_HW_16 ) {
    snd_printdd( "SB 16 soundcard weren't detected at 0x%x\n", port );
    __return1:
    snd_pcm_free( pcm );
    snd_unregister_ioports( card );
    return NULL;
  }
  if ( mpu_flag )
    codec -> mpu_port = snd_mpu_port[ dev ];
  if ( snd_sbdsp_sb16_configure( pcm ) < 0 ) {
    goto __return1;
  }
  return pcm;
}

static void snd_sb16_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  struct snd_sb16 *acard;
#ifndef SND_TEST_SB_OLD_MIDI
  snd_pcm_t *pcm;
  sbdsp_t *sbdsp;
  unsigned long flags;
  register unsigned short status;
  
  acard = (struct snd_sb16 *)dev_id;
  if ( acard == NULL ) return;
  pcm = acard -> pcm;
  if ( pcm == NULL ) return;
  sbdsp = (sbdsp_t *)pcm -> private_data;
  snd_spin_lock( &sbdsp -> mixer, mixer, &flags );
  status = snd_sbmixer_read( &sbdsp -> mixer, 0x82 );
  snd_spin_unlock( &sbdsp -> mixer, mixer, &flags );
#if 0
  snd_printd( "status = 0x%x\n", status );
#endif
  if ( (status & 0x04) && acard -> rmidi )
    snd_mpu401_uart_interrupt( acard -> rmidi );
  if ( status & 0x03 )
    snd_sbdsp_sb16_interrupt( pcm, status );
#else
  acard = (struct snd_sb16 *)dev_id;
  if ( acard == NULL ) return;
  if ( acard -> rmidi )
    snd_sbdsp_midi_interrupt( acard -> rmidi );
#endif
}

static int snd_sb16_resources( int dev, struct snd_sb16 *acard, snd_card_t *card )
{
  static int possible_irqs[] = { 5, 9, 10, 7, -1 };
  static int possible_dmas8[] = { 1, 3, 0, -1 };
  static int possible_dmas16[] = { 5, 6, 7, -1 };
  int ok = 0;

  if ( (acard -> irqnum = snd_register_interrupt( card, "Sound Blaster 16", snd_irq[ dev ], SND_IRQ_TYPE_ISA, snd_sb16_interrupt, acard, possible_irqs )) < 0 )
    return acard -> irqnum;
  if ( (snd_dma8[ dev ] >= 0 && snd_dma8[ dev ] <= 3) || snd_dma8[ dev ] == SND_AUTO_DMA ) {
    if ( (acard -> dma8num = snd_register_dma_channel( card, "Sound Blaster 16", snd_dma8[ dev ], SND_DMA_TYPE_ISA, snd_dma8_size[ dev ], possible_dmas8 )) < 0 ) {
      return acard -> dma8num;
    }
    ok++;
  } else {
    acard -> dma8num = SND_DMA_DISABLE;
  }
  if ( (snd_dma16[ dev ] >= 4 && snd_dma16[ dev ] <= 7) || snd_dma16[ dev ] == SND_AUTO_DMA ) {
    if ( (acard -> dma16num = snd_register_dma_channel( card, "Sound Blaster 16", snd_dma16[ dev ], SND_DMA_TYPE_ISA, snd_dma16_size[ dev ], possible_dmas16 )) < 0 ) {
      return acard -> dma16num;
    }
    ok++;
  } else {
    acard -> dma16num = SND_DMA_DISABLE;
  }
  if ( !ok ) {
    snd_printk( "SB16 - bad DMA #\n" );
    return -ENOMEM;
  }
  return 0;
}

static int snd_sb16_probe( int dev, struct snd_sb16 *acard )
{
#ifndef SNDCFG_PNP
  static int possible_ports[] = { 0x220,0x240,0x260,-1 };
  int *ports = possible_ports;
#endif
  sbdsp_t *codec;
  snd_card_t *card;
  snd_pcm_t *pcm = NULL;
  snd_kmixer_t *mixer = NULL;
  snd_rawmidi_t *rmidi = NULL;

  card = snd_card_new( snd_index[ dev ], snd_id[ dev ],
  		       snd_sb16_use_inc, snd_sb16_use_dec );
  if ( !card ) return -ENOMEM;
  card -> static_data = acard;
  card -> type = SND_CARD_TYPE_SB_16;		/* overriden in probe function */
  if ( snd_sb16_resources( dev, acard, card ) < 0 ) {
    snd_card_free( card );
    return -EBUSY;
  }
  pcm = NULL;
#ifndef SNDCFG_PNP
  if ( snd_port[ dev ] == SND_AUTO_PORT ) {
    for ( ports = possible_ports; *ports >= 0; ports++ ) {
      pcm = snd_sb16_detect( dev, card, *ports, acard -> irqnum, acard -> dma8num, acard -> dma16num );
      if ( pcm ) break;
    }
    if ( !pcm ) {
      snd_card_free( card );
      return -ENODEV;
    }
  } else {
#endif
    pcm = snd_sb16_detect( dev, card, snd_port[ dev ], acard -> irqnum, acard -> dma8num, acard -> dma16num );
    if ( !pcm ) {
      snd_card_free( card );
      return -ENODEV;
    }
#ifndef SNDCFG_PNP
  }
#endif
  codec = (sbdsp_t *)pcm -> private_data;
  mixer = snd_sbdsp_new_mixer( card, &codec -> mixer, codec -> hardware );
  if ( !mixer ) goto __nodev;

#ifndef SND_TEST_SB_OLD_MIDI
  if ( codec -> mpu_port ) {
    rmidi = snd_mpu401_uart_new_device( card, MPU401_HW_SB, codec -> mpu_port, codec -> irq );
    if ( !rmidi ) goto __nodev;
  }
#else
  rmidi = snd_sbdsp_midi_new_device( card, pcm );
  if ( !rmidi ) goto __nodev;
#endif

  if ( rmidi && snd_rawmidi_register( rmidi, 0 ) < 0 ) goto __nodev;
  if ( snd_mixer_register( mixer, 0 ) < 0 ) {
    if ( rmidi ) snd_rawmidi_unregister( rmidi ); rmidi = NULL;
    goto __nodev;
  }
  if ( snd_pcm_register( pcm, 0 ) ) {
    if ( rmidi ) snd_rawmidi_unregister( rmidi ); rmidi = NULL;
    snd_mixer_unregister( mixer ); mixer = NULL;
    goto __nodev;
  }

  /* setup Mic AGC */
  snd_sbmixer_write( &codec -> mixer, 0x43, (snd_sbmixer_read( &codec -> mixer, 0x43 ) & 0x01) | (snd_mic_agc[ dev ] ? 0x00 : 0x01) );

#if 0
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_MASTER, 70, 70, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_BASS, 50, 50, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_TREBLE, 25, 25, 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_SPEAKER, 0, 0, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_LINE, 0, 0, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_MIC, 0, 0, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_CD, 0, 0, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_IGAIN, 0, 0, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_OGAIN, 0, 0, 0 );
#endif

  snd_enable_irq( card, acard -> irqnum );
  strcpy( card -> abbreviation, "SB16" );
  strcpy( card -> shortname, "Sound Blaster 16" );
  sprintf( card -> longname, "%s at 0x%x, irq %i, dma ",
  	codec -> name,
  	codec -> port,
  	card -> irqs[ acard -> irqnum ] -> irq );
  if ( acard -> dma8num != SND_DMA_DISABLE )
    sprintf( card -> longname + strlen( card -> longname ), "%i",
    		card -> dmas[ acard -> dma8num ] -> dma );
  if ( acard -> dma16num != SND_DMA_DISABLE )
    sprintf( card -> longname + strlen( card -> longname ), "%s%i",
    		acard -> dma8num != SND_DMA_DISABLE ? "&" : "",
    		card -> dmas[ acard -> dma16num ] -> dma );
  if ( !snd_card_register( card ) ) {
    acard -> card = card;
    acard -> pcm = pcm;
    acard -> mixer = mixer;
    acard -> rmidi = rmidi;
    return 0;
  }
  snd_pcm_unregister( pcm ); pcm = NULL;
  snd_mixer_unregister( mixer ); mixer = NULL;

  __nodev:
  if ( rmidi ) snd_rawmidi_free( rmidi );
  if ( mixer ) snd_mixer_free( mixer );
  if ( pcm ) snd_pcm_free( pcm );
  snd_card_free( card );
  return -ENXIO;
}

static int snd_sb16_free( int dev )
{
  struct snd_sb16 *acard;

  acard = snd_sb16_cards[ dev ];
  snd_sb16_cards[ dev ] = NULL;
  if ( acard ) {
    snd_card_unregister( acard -> card );
    if ( acard -> rmidi )
      snd_rawmidi_unregister( acard -> rmidi );
    if ( acard -> mixer )
      snd_mixer_unregister( acard -> mixer );
    if ( acard -> pcm ) {
      snd_pcm_unregister( acard -> pcm );
    }
    snd_card_free( acard -> card );
    snd_free( acard, sizeof( struct snd_sb16 ) );
  }
  return 0;
}

#ifdef SNDCFG_PNP

static struct snd_sb16 *snd_sb16_pnp_card( struct pnp_device *d, int *rdev )
{
  int dev;
  struct snd_sb16 *acard;
    
  if ( rdev ) *rdev = -1;
  for ( dev = 0; dev < SND_CARDS; dev++ ) {
    acard = snd_sb16_cards[ dev ];
    if ( !acard ) continue;
    if ( acard -> pnpdev != d ) continue;
    *rdev = dev;
    return acard;
  }
  return NULL;
}

static void snd_sb16_pnp_configure( int dev, struct pnp_resource *r )
{
  int nio = 0, ndma = 0;

  for ( ; r != NULL; r = r -> next ) {
    switch ( r -> type ) {
      case PNP_RES_IO:
        if ( nio == 0 ) snd_port[ dev ] = r -> start;
        nio++;
        break;
      case PNP_RES_DMA:
        if ( ndma == 0 ) snd_dma8[ dev ] = r -> start; else
        if ( ndma == 1 ) snd_dma16[ dev ] = r -> start;
        ndma++;
        break;
      case PNP_RES_IRQ:
        snd_irq[ dev ] = r -> start;
        break;
      default:
        break;
    }
  }
}

static int snd_sb16_pnp_event( struct pnp_device *d, struct pnp_drv_event *e )
{
  int dev = -1;
  struct snd_sb16 *acard = NULL;

#if 0
  printk( "PNP event = %i, d = 0x%lx\n", e -> type, (long)d );
#endif
  switch ( e -> type ) {
    case PNP_DRV_DISABLE:
    case PNP_DRV_EMERGSTOP:
      acard = snd_sb16_pnp_card( d, &dev );
      if ( !acard || dev < 0 ) return -ENODEV;
      return snd_sb16_free( dev );
    case PNP_DRV_ALLOC:
      for ( dev = 0; dev < SND_CARDS; dev++ )
        if ( snd_sb16_cards[ dev ] == NULL ) break;
      if ( dev >= SND_CARDS ) return -ENOMEM;
      acard = snd_malloc( sizeof( struct snd_sb16 ) );
      if ( !acard ) return -ENOMEM;
      memset( acard, 0, sizeof( struct snd_sb16 ) );
      snd_sb16_pnp_configure( dev, d -> res );
      if ( snd_sb16_probe( dev, acard ) < 0 ) {
        snd_printk( "Oops.. Fatal error.. SB 16 PnP soundcard can't be found with PnP method..\n" );
        snd_free( acard, sizeof( struct snd_sb16 ) );
        return -ENXIO;
      }
      acard -> pnpdev = d;
      snd_sb16_cards[ dev ] = acard;
      return 0;
    case PNP_DRV_RECONFIG:
      return 0;
    case PNP_DRV_CONFIG:
      acard = snd_sb16_pnp_card( d, &dev );
      if ( !acard || dev < 0 ) return -ENODEV;
      snd_sb16_free( dev );
      acard = snd_malloc( sizeof( struct snd_sb16 ) );
      if ( !acard ) {
        return -ENOMEM;
      }
      memset( acard, 0, sizeof( struct snd_sb16 ) );
      snd_sb16_pnp_configure( dev, d -> res );
      if ( snd_sb16_probe( dev, acard ) < 0 ) {
        snd_printk( "Oops.. Fatal error.. SB 16 PnP soundcard can't be found with PnP method..\n" );
        snd_free( acard, sizeof( struct snd_sb16 ) );
        return -ENXIO;
      }
      acard -> pnpdev = d;
      snd_sb16_cards[ dev ] = acard;
      return 0;
  }
  return 0;
}

#endif

int init_module( void )
{
  int dev, cards;
  struct snd_sb16 *acard;

#ifndef LINUX_2_1
  register_symtab( NULL );
#endif
  for ( dev = cards = 0; dev < SND_CARDS && snd_port[ dev ] > 0; dev++ ) {
    acard = (struct snd_sb16 *)snd_malloc( sizeof( struct snd_sb16 ) );
    if ( !acard ) continue;
    memset( acard, 0, sizeof( struct snd_sb16 ) );
    if ( snd_sb16_probe( dev, acard ) < 0 ) {
      snd_free( acard, sizeof( struct snd_sb16 ) );
      if ( snd_port[ dev ] == SND_AUTO_PORT ) break;
      snd_printk( "Sound Blaster 16 soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[ dev ] );
      continue;
    }
    snd_sb16_cards[ dev ] = acard;
    cards++;
  }
#ifdef SNDCFG_PNP
  memset( &snd_sb16_pnp, 0, sizeof( snd_sb16_pnp ) );
  snd_sb16_pnp.id.type = PNP_HDL_ISA;
  snd_sb16_pnp.id.t.isa.id = PNP_EISAID( 'C', 'T', 'L', 0x0031 );
  snd_sb16_pnp.id.next = NULL;
  snd_sb16_pnp_awe64_id = snd_sb16_pnp.id;
  snd_sb16_pnp_awe64_id.t.isa.id = PNP_EISAID( 'C', 'T', 'L', 0x0045 );
  snd_sb16_pnp.id.next = &snd_sb16_pnp_awe64_id;    
  snd_sb16_pnp.event = snd_sb16_pnp_event;
  snd_sb16_pnp.name = "Sound Blaster 16 PnP / AWE PnP";  
  if ( pnp_register_driver( &snd_sb16_pnp, 1 ) < 0 ) {
    pnp_unregister_driver( &snd_sb16_pnp );
    return -ENOMEM;
  }
#else
  if ( !cards ) { 
    snd_printk( "Sound Blaster 16 soundcard #%i not found or device busy\n", dev + 1 );
    return -ENODEV;
  }
#endif
  return 0;
}

void cleanup_module( void )
{
  int dev;

#ifdef SNDCFG_PNP
  if ( (dev = pnp_unregister_driver( &snd_sb16_pnp )) < 0 )
    snd_printk( "pnp_unregister_driver failure (%i)\n", dev );
#endif
  for ( dev = 0; dev < SND_CARDS; dev++ )
    snd_sb16_free( dev );
}
