/*
 *  Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible
 *  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 "midi.h"
#include "sb.h"
#include "initval.h"

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_PORT;	/* 0x220,0x240,0x260 */
int snd_irq[ SND_CARDS ] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_dma8[ SND_CARDS ] = SND_DEFAULT_DMA;	/* 1,3 */
int snd_dma8_size[ SND_CARDS ] = SND_DEFAULT_DMA_SIZE; /* 8,16,32,64,128 */
#ifdef MODULE_PARM
MODULE_PARM( snd_index, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_index, "Index value for Sound Blaster soundcard." );
MODULE_PARM( snd_id, "1-" __MODULE_STRING(SND_CARDS) "s" );
MODULE_PARM_DESC( snd_id, "ID string for Sound Blaster soundcard." );
MODULE_PARM( snd_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_port, "Port # for SB8 driver." );
MODULE_PARM( snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_irq, "IRQ # for SB8 driver." );
MODULE_PARM( snd_dma8, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma8, "8-bit DMA # for SB8 driver." );
#endif

static struct snd_sb8 {
  int irqnum;
  int dma8num;
  int dma16num;
  snd_card_t *card;
  snd_pcm_t *pcm;
  snd_kmixer_t *mixer;
  snd_rawmidi_t *rmidi;
} *snd_sb8_cards[ SND_CARDS ] = SND_DEFAULT_PTR;

static void snd_sb8_use_inc( snd_card_t *card )
{
  MOD_INC_USE_COUNT;
}

static void snd_sb8_use_dec( snd_card_t *card )
{
  MOD_DEC_USE_COUNT;
}

static snd_pcm_t *snd_sb8_detect( snd_card_t *card,
                                  unsigned short port,
                                  unsigned short irqnum,
                                  unsigned short dma8num,
                                  unsigned short dma16num )
{
  snd_pcm_t *pcm;
  sbdsp_t *codec;

  if ( snd_register_ioport( card, port, 16, "Sound Blaster" ) < 0 )
    return NULL;
  pcm = snd_sbdsp_new_device( card, port, irqnum, dma8num, SND_DMA_DISABLE, SB_HW_10 );
  if ( !pcm ) {
    snd_unregister_ioports( card );
    return NULL;
  }
  codec = (sbdsp_t *)pcm -> private_data;
  if ( codec -> hardware >= SB_HW_16 ) {
    snd_printk( "SB 16 soundcard detected at 0x%x, use SB16 driver\n", port );
    snd_pcm_free( pcm );
    snd_unregister_ioports( card );
    return NULL;
  }
  return pcm;
}

static void snd_sb8_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  struct snd_sb8 *acard;
  snd_pcm_t *pcm;
  sbdsp_t *codec;
  
  acard = (struct snd_sb8 *)dev_id;
  if ( acard == NULL ) return;
  pcm = acard -> pcm;
  if ( pcm == NULL ) return;
  codec = (sbdsp_t *)pcm -> private_data;
  if ( codec == NULL ) return;
  if ( codec -> open8 & SB_OPEN_PCM ) {
    snd_sbdsp_sb8_interrupt( pcm );
  } else {
    if ( acard -> rmidi )
      snd_sbdsp_midi_interrupt( acard -> rmidi );
  }
}

static int snd_sb8_resources( int dev, struct snd_sb8 *acard, snd_card_t *card )
{
  static int possible_irqs[] = { -1 };
  static int possible_dmas8[] = { -1 };

  if ( (acard -> irqnum = snd_register_interrupt( card, "Sound Blaster", snd_irq[ dev ], SND_IRQ_TYPE_ISA, snd_sb8_interrupt, acard, possible_irqs )) < 0 ) {
    return acard -> irqnum;
  }
  if ( snd_dma8[ dev ] >= 0 ) {
    if ( (acard -> dma8num = snd_register_dma_channel( card, "Sound Blaster", snd_dma8[ dev ], SND_DMA_TYPE_ISA, snd_dma8_size[ dev ], possible_dmas8 )) < 0 ) {
      return acard -> dma8num;
    }
  } else {
    return -ENOMEM;
  }
  acard -> dma16num = SND_DMA_DISABLE;
  return 0;
}

static int snd_sb8_probe( int dev, struct snd_sb8 *acard )
{
  static int possible_ports[] = { 0x220,0x240,0x260,-1 };
  int *ports = possible_ports;
  sbdsp_t *codec;
  snd_card_t *card;
  snd_pcm_t *pcm = NULL;
  snd_kmixer_t *mixer = NULL;
  snd_rawmidi_t *rmidi = NULL;
  char *str;

  card = snd_card_new( snd_index[ dev ], snd_id[ dev ],
                       snd_sb8_use_inc, snd_sb8_use_dec );
  if ( !card ) return -ENOMEM;
  card -> type = SND_CARD_TYPE_SB_10;		/* overriden in probe function */
  if ( snd_sb8_resources( dev, acard, card ) < 0 ) {
    snd_card_free( card );
    return -EBUSY;
  }
  pcm = NULL;
  if ( snd_port[ dev ] == SND_AUTO_PORT ) {
    for ( ports = possible_ports; *ports >= 0; ports++ ) {
      pcm = snd_sb8_detect( card, *ports, acard -> irqnum, acard -> dma8num, acard -> dma16num );
      if ( pcm ) break;
    }
    if ( !pcm ) {
      snd_card_free( card );
      return -ENODEV;
    }
  } else {
    pcm = snd_sb8_detect( card, snd_port[ dev ], acard -> irqnum, acard -> dma8num, acard -> dma16num );
    if ( !pcm ) {
      snd_card_free( card );
      return -ENODEV;
    }
  }
  codec = (sbdsp_t *)pcm -> private_data;
  mixer = snd_sbdsp_new_mixer( card, &codec -> mixer, codec -> hardware );
  if ( mixer ) {
    if ( snd_mixer_register( mixer, 0 ) < 0 ) goto __nodev;
#if 0
    snd_mixer_channel_init( mixer, SND_MIXER_PRI_MASTER, 70, 70, 0 );
    snd_mixer_channel_init( mixer, SND_MIXER_PRI_FM, 75, 75, 0 );
    snd_mixer_channel_init( mixer, SND_MIXER_PRI_PCM, 85, 85, 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 );
#endif
  }

  rmidi = snd_sbdsp_midi_new_device( card, pcm );
  if ( !rmidi || snd_rawmidi_register( rmidi, 0 ) ) {
    if ( mixer ) snd_mixer_unregister( mixer ); mixer = NULL;
    goto __nodev;
  }

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

  snd_enable_irq( card, acard -> irqnum );
  strcpy( card -> abbreviation, "SB" );
  strcpy( card -> shortname, codec -> name );
  str = card -> shortname;
  while ( *str && *str != '(' ) str++;
  if ( *str == '(' && str != card -> shortname ) *(str - 1) = 0;
  sprintf( card -> longname, "%s at 0x%x, irq %i, dma %i",
  	codec -> name,
  	codec -> port,
  	card -> irqs[ acard -> irqnum ] -> irq,
  	card -> dmas[ acard -> dma8num ] -> 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;
  if ( mixer ) 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;
}

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

#ifndef LINUX_2_1
  register_symtab( NULL );
#endif
  for ( dev = cards = 0; dev < SND_CARDS && snd_port[ dev ] > 0; dev++ ) {
    acard = (struct snd_sb8 *)snd_malloc( sizeof( struct snd_sb8 ) );
    if ( !acard ) continue;
    memset( acard, 0, sizeof( struct snd_sb8 ) );
    if ( snd_sb8_probe( dev, acard ) < 0 ) {
      snd_free( acard, sizeof( struct snd_sb8 ) );
      if ( snd_port[ dev ] == SND_AUTO_PORT ) break;
      snd_printk( "Sound Blaster soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[ dev ] );
      continue;
    }
    snd_sb8_cards[ dev ] = acard;
    cards++;
  }
  if ( !cards ) { 
    snd_printk( "Sound Blaster soundcard #%i not found or device busy\n", dev + 1 );
    return -ENODEV;
  }
  return 0;
}

void cleanup_module( void )
{
  int idx;
  struct snd_sb8 *acard;

  for ( idx = 0; idx < SND_CARDS; idx++ ) {
    acard = snd_sb8_cards[ idx ];
    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_sb8 ) );
    }
  }
}
