/*
 *  Driver for generic ESS AudioDrive ESx688 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"
#include "pcm.h"
#include "mixer.h"
#include "midi.h"
#include "es1688.h"
#include "mpu401.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_mpu_port[ SND_CARDS ] = { [0 ... (SND_CARDS-1)] = -1 };
int snd_irq[ SND_CARDS ] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_mpu_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 */
#ifdef MODULE_PARM
MODULE_PARM( snd_index, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_index, "Index value for ESx688 soundcard." );
MODULE_PARM( snd_id, "1-" __MODULE_STRING(SND_CARDS) "s" );
MODULE_PARM_DESC( snd_id, "ID string for ESx688 soundcard." );
MODULE_PARM( snd_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_port, "Port # for ESx688 driver." );
MODULE_PARM( snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_mpu_port, "MPU-401 port # for ESx688 driver." );
MODULE_PARM( snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_irq, "IRQ # for ESx688 driver." );
MODULE_PARM( snd_mpu_irq, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_mpu_irq, "MPU-401 IRQ # for ESx688 driver." );
MODULE_PARM( snd_dma8, "1-" __MODULE_STRING(SND_CARDS) "i" );
MODULE_PARM_DESC( snd_dma8, "8-bit DMA # for ESx688 driver." );
#endif

static struct snd_audiodrive {
  int irqnum;
  int mpuirqnum;
  int dma8num;
  snd_card_t *card;
  snd_pcm_t *pcm;
  snd_kmixer_t *mixer;
  snd_rawmidi_t *rmidi;
} *snd_audiodrive_cards[ SND_CARDS ] = SND_DEFAULT_PTR;

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

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

static snd_pcm_t *snd_audiodrive_detect( snd_card_t *card,
                                         unsigned short port,
                                         unsigned short mpu_port,
                                         unsigned short irqnum,
                                         unsigned short mpu_irqnum,
                                         unsigned short dma8num )
{
  snd_pcm_t *pcm;
  es1688_t *codec;

  if ( snd_register_ioport( card, port, 16, "ESx688" ) < 0 )
    return NULL;
  if ( mpu_irqnum != SND_IRQ_DISABLE ) {
    if ( snd_register_ioport( card, mpu_port, 2, "ESx688 - MPU-401" ) < 0 ) {
      snd_unregister_ioports( card );
      snd_printk( "ports for ESx688 MPU-401 are already used, try another\n" );
      return NULL;
    }
  }
  pcm = snd_es1688_new_device( card, port, mpu_port, irqnum, mpu_irqnum, dma8num, ES1688_HW_AUTO );
  if ( !pcm ) {
    snd_unregister_ioports( card );
    return NULL;
  }
  codec = (es1688_t *)pcm -> private_data;
  if ( codec -> hardware == ES1688_HW_688 ) {
    if ( mpu_irqnum != SND_IRQ_DISABLE ) {
      snd_printk( "fatal: ES688 chip doesn't have MPU401\n" );
      snd_pcm_free( pcm );
      snd_unregister_ioports( card );
      return NULL;
    }
  }
  return pcm;
}

static void snd_audiodrive_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  struct snd_audiodrive *acard;
  
  acard = (struct snd_audiodrive *)dev_id;
  if ( acard == NULL || acard -> pcm == NULL ) return; 
  snd_es1688_interrupt( acard -> pcm );
}

static void snd_audiodrive_midi_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  struct snd_audiodrive *acard;
  
  acard = (struct snd_audiodrive *)dev_id;
  if ( acard == NULL || acard -> rmidi == NULL ) return; 
  snd_mpu401_uart_interrupt( acard -> rmidi );
}

static int snd_audiodrive_resources( int dev, struct snd_audiodrive *acard, snd_card_t *card )
{
  static int possible_irqs[] = { 5, 9, 10, 7, -1 };
  static int possible_dmas[] = { 1, 3, 0, -1 };

  if ( (acard -> irqnum = snd_register_interrupt( card, "ESx688", snd_irq[ dev ], SND_IRQ_TYPE_ISA, snd_audiodrive_interrupt, acard, possible_irqs )) < 0 )
    return acard -> irqnum;
  acard -> mpuirqnum = SND_IRQ_DISABLE;
  if ( snd_mpu_port[ dev ] >= 0x300 && snd_mpu_port[ dev ] <= 0x330 && (snd_mpu_port[ dev ] & 0x00f) == 0 ) {
    if ( (acard -> mpuirqnum = snd_register_interrupt( card, "ESx688 - MPU401", snd_mpu_irq[ dev ], SND_IRQ_TYPE_ISA, snd_audiodrive_midi_interrupt, acard, possible_irqs )) < 0 ) {
      acard -> mpuirqnum = SND_IRQ_DISABLE;
      snd_printk( "warning: can't allocate IRQ for MPU401, midi port is disabled\n" );
    }
  }
  if ( (acard -> dma8num = snd_register_dma_channel( card, "ESx688", snd_dma8[ dev ], SND_DMA_TYPE_ISA, snd_dma8_size[ dev ], possible_dmas )) < 0 ) {
    return acard -> dma8num;
  }
  return 0;
}

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

  card = snd_card_new( snd_index[ dev ], snd_id[ dev ],
  		       snd_audiodrive_use_inc, snd_audiodrive_use_dec );
  if ( !card ) return -ENOMEM;
  card -> type = SND_CARD_TYPE_ESS_ES1688;
  if ( snd_audiodrive_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_audiodrive_detect( card, *ports, snd_mpu_port[ dev ], acard -> irqnum, acard -> mpuirqnum, acard -> dma8num );
      if ( pcm ) break;
    }
    if ( !pcm ) {
      snd_card_free( card );
      return -ENODEV;
    }
  } else {
    pcm = snd_audiodrive_detect( card, snd_port[ dev ], snd_mpu_port[ dev ], acard -> irqnum, acard -> mpuirqnum, acard -> dma8num );
    if ( !pcm ) {
      snd_card_free( card );
      return -ENODEV;
    }
  }

  codec = (es1688_t *)pcm -> private_data;

  mixer = snd_es1688_new_mixer( pcm );
  if ( !mixer ) goto __nodev;

  if ( acard -> mpuirqnum != SND_IRQ_DISABLE ) {
    rmidi = snd_mpu401_uart_new_device( card, MPU401_HW_ES1688, codec -> mpu_port, codec -> mpu_irq );
    if ( !rmidi ) goto __nodev;
    if ( 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;
  }
  if ( snd_es1688_init( pcm, 1 ) < 0 ) {
    if ( rmidi ) snd_rawmidi_unregister( rmidi ); rmidi = NULL;
    snd_mixer_unregister( mixer ); mixer = NULL;
    snd_pcm_unregister( pcm ); pcm = NULL;
    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, 70, 70, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_MIC, 70, 70, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_CD, 70, 70, 0 );
  snd_mixer_channel_init( mixer, SND_MIXER_PRI_AUXA, 0, 0, SND_MIXER_FLG_MUTE );
#endif
  snd_enable_irq( card, acard -> irqnum );
  if ( acard -> mpuirqnum != SND_IRQ_DISABLE )
    snd_enable_irq( card, acard -> mpuirqnum );
  strcpy( card -> abbreviation, "AudioDrive" );
  strcpy( card -> shortname, pcm -> name );
  sprintf( card -> longname, "%s at 0x%x, irq %i, dma %i",
  	pcm -> 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_mixer_unregister( mixer );
  snd_pcm_unregister( pcm );
  snd_card_free( card );
  return -ENOMEM;
  
  __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_audiodrive *acard;

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

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

  for ( idx = 0; idx < SND_CARDS; idx++ ) {
    acard = snd_audiodrive_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_es1688_init( acard -> pcm, 0 );
        snd_pcm_unregister( acard -> pcm );
      }
      snd_card_free( acard -> card );
      snd_free( acard, sizeof( struct snd_audiodrive ) );
    }
  }
}
