/*
 *  Driver for Mozart (OAK OTI-601) soundcards
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *
 *  Driver supports only one soundcard per system (soundcard limitation).
 *
 *  Driver isn't complete. I don't have documentation and these things
 *  are not working:
 *
 *     1) Line-In input
 *     2) MIDI port
 *
 *   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_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "pcm.h"
#include "mixer.h"
#include "midi.h"
#include "sb.h"
#include "ad1848.h"
#include "initval.h"

#if 0
#define MOZART_DEBUG
#endif
#if 0
#define MOZART_SB_MIDI
#endif

/*
 *
 */

int snd_index = SND_DEFAULT_IDX1;		/* Index 1-MAX */
char *snd_id = SND_DEFAULT_STR1;		/* ID for this card */
int snd_port = SND_DEFAULT_PORT1;		/* 0x530,0xe80,0xf40,0x604 */
int snd_irq = SND_DEFAULT_IRQ1;			/* 7,9,10,11 */
#ifdef MOZART_SB_MIDI
int snd_irq1 = SND_DEFAULT_IRQ1;		/* 5,7,11 */
#endif
int snd_dma1 = SND_DEFAULT_DMA1;		/* 0,1,3 */
int snd_dma1_size = SND_DEFAULT_DMA_SIZE1;	/* 8,16,32,64 */
#ifdef MODULE_PARM
MODULE_PARM( snd_index, "i" );
MODULE_PARM_DESC( snd_index, "Index value for Mozart soundcard." );
MODULE_PARM( snd_id, "s" );
MODULE_PARM_DESC( snd_id, "ID string for Mozart soundcard." );
MODULE_PARM( snd_port, "i" );
MODULE_PARM_DESC( snd_port, "Port # for Mozart driver." );
MODULE_PARM( snd_irq, "i" );
MODULE_PARM_DESC( snd_irq, "IRQ # for Mozart driver." );
#ifdef MOZART_SB_MIDI
MODULE_PARM( snd_irq1, "i" );
MODULE_PARM_DESC( snd_irq1, "Midi IRQ # for Mozart driver." );
#endif
MODULE_PARM( snd_dma1, "i" );
MODULE_PARM_DESC( snd_dma1, "DMA1 # for Mozart driver." );
MODULE_PARM( snd_dma1_size, "i" );
MODULE_PARM_DESC( snd_dma1_size, "DMA1 size in kB for Mozart driver." );
#endif

/*
 *
 */

#define MOZART_REG		0xf8c
#define MOZART_REG_PASSWORD	0xf8f
 
/*
 *
 */

static int snd_irqnum = SND_IRQ_DISABLE;
static int snd_dma1num = SND_DMA_DISABLE;
static snd_card_t *snd_card = NULL;
#ifdef MOZART_SB_MIDI
static int snd_sbport = -1;
static int snd_irq1num = SND_IRQ_DISABLE;
static snd_pcm_t *snd_sbpcm = NULL;
#endif
static snd_pcm_t *snd_pcm = NULL;
static snd_kmixer_t *snd_mixer = NULL;
static unsigned short snd_pcm_status_reg = 0;

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

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

static unsigned char snd_mozart_read( int index )
{
  unsigned long flags;
  unsigned char result;

  snd_cli( &flags );
  outb( 0xe2, MOZART_REG_PASSWORD );
  result = inb( MOZART_REG + index );
  snd_sti( &flags ); 
#ifdef MOZART_DEBUG
  snd_printk( "mozart_read( %i ) == 0x%x\n", index, result );
#endif
  return result;
}

static void snd_mozart_write( int index, unsigned char val )
{
  unsigned long flags;

#ifdef MOZART_DEBUG
  snd_printk( "mozart_write( %i, 0x%x )\n", index, val );
#endif
  snd_cli( &flags );
  outb( 0xe2, MOZART_REG_PASSWORD );
  outb( val, MOZART_REG + index );
  snd_sti( &flags ); 
}

static int snd_mozart_detect( void )
{
  static int possible_ports[] = { 0x530, 0xe80, 0xf40, 0x604 };
  static int interrupt_bits[] = { -1, -1, -1, -1, -1, -1, -1, 0x08, -1, 0x10, 0x18, 0x20, -1, -1, -1, -1 };
  static int dma_bits[] = { 1, 2, 0, 3 };
  int tmp, tmp1, portsel;

  if ( snd_register_ioport( snd_card, MOZART_REG, 8, "Mozart - control" ) < 0 )
    return -EBUSY;
  if ( snd_port == SND_AUTO_PORT ) {
    for ( portsel = 0; portsel < 4; portsel++ ) {
      if ( snd_register_ioport( snd_card, snd_port = possible_ports[ portsel ], 8, "Mozart - WSS" ) >= 0 ) {
        break;
      }
    }
    if ( portsel >= 4 ) {
      snd_unregister_ioports( snd_card );
      return -EBUSY;
    }
  } else {
    for ( portsel = 0; portsel < 4; portsel++ )
      if ( possible_ports[ portsel ] == snd_port ) break;
    if ( portsel >= 4 ) return -EINVAL;
    if ( snd_register_ioport( snd_card, snd_port, 8, "Mozart - WSS" ) < 0 ) {
      snd_unregister_ioports( snd_card );
      return -EBUSY;
    }
  }
  /* try detect OAK chip */  
  if ( (tmp = snd_mozart_read( 1 )) == 0xff ) {
    snd_printdd( "mozart: reg 1 == 0xff\n" );
    snd_unregister_ioports( snd_card );
    return -ENODEV;
  }
  if ( (tmp1 = inb( MOZART_REG + 1 )) == tmp ) {
    snd_printdd( "mozart: reg 1: tmp1 == tmp == 0x%x\n", tmp );
    snd_unregister_ioports( snd_card );
    return -ENODEV;
  }
  snd_mozart_write( 1, tmp ^ 0x80 );
  tmp1 = snd_mozart_read( 1 );
  snd_mozart_write( 1, tmp );
  if ( (tmp ^ 0x80) != tmp1 ) {
    snd_printdd( "mozart: reg 1: invert failed\n" );
    snd_unregister_ioports( snd_card );
    return -ENODEV;
  }
  if ( (snd_mozart_read( 3 ) & 0x03) != 0x03 ) {
    snd_printdd( "mozart: reg 3: invalid value == 0x%x\n", snd_mozart_read( 3 ) );
    return -ENODEV;
  }
#if 0
  /* write map... */
  printk( "mozart: map [0x00]: " );
  for ( tmp = 0; tmp < 8; tmp++ ) {
    printk( "%02x:", snd_mozart_read( tmp ) );
  }
  printk( "\n" );
#endif
  /* ok.. OAK chip found.. */
  snd_mozart_write( 1, 0x8f | (portsel << 4) );	/* enable WSS, disable SB */
  snd_mozart_write( 2, 0x03 );
#if 0
  snd_mozart_write( 4, 0x02 );
  snd_mozart_write( 5, 0x30 );			/* or 0x32? (sync for codec) */
#else
  snd_mozart_write( 4, 0x00 );
  snd_mozart_write( 5, 0x00 );
  snd_mozart_write( 6, 0x00 );
  snd_mozart_write( 7, 0x00 );
#endif
  /* initialize IRQ for WSS codec */
  tmp = interrupt_bits[ snd_card -> irqs[ snd_irqnum ] -> irq ];
  if ( tmp < 0 ) return -EINVAL;
  outb( tmp | 0x40, snd_port );
  tmp1 = dma_bits[ snd_card -> dmas[ snd_dma1num ] -> dma ];
  outb( tmp | tmp1, snd_port );
#ifdef MOZART_SB_MIDI
  /* initialize SB (if needed for midi) */
  if ( snd_irq1num != SND_IRQ_DISABLE ) {
    snd_sbport = portsel & 2 ? snd_sbport = 0x240 : 0x220;
    tmp = 0;
    switch ( snd_card -> irqs[ snd_irq1num ] -> irq ) {
      case 5: tmp = 0x80 | 0x30 | 0x04; break;
      case 7: tmp = 0x00 | 0x30 | 0x04; break;
      case 11: tmp = 0x40 | 0x30 | 0x04; break;
      default: return -EINVAL;
    }
    snd_mozart_write( 3, tmp );
    if ( snd_register_ioport( snd_card, snd_sbport, 16, "Mozart - SB" ) < 0 ) {
      snd_unregister_ioports( snd_card );
      return -EBUSY;
    }
  } else {
    snd_mozart_write( 3, 0x03 );	/* default settings */
  }
#else
  snd_mozart_write( 3, 0x03 );		/* default settings */
#endif
#if 0
  snd_mozart_write( 1, 0x0f );
  snd_mozart_write( 2, 0x03 );
  snd_mozart_write( 3, 0x03 );
  snd_mozart_write( 4, 0x00 );
  snd_mozart_write( 5, 0x00 );
  snd_mozart_write( 6, 0x00 );
  snd_mozart_write( 7, 0x00 );
#endif
#if 0
  /* write map... */
  printk( "mozart: map [0x00]: " );
  for ( tmp = 0; tmp < 8; tmp++ ) {
    printk( "%02x:", snd_mozart_read( tmp ) );
  }
  printk( "\n" );
#endif
  return 0;
}

static void snd_mozart_interrupt( int irq, void *dev_id, struct pt_regs *regs )
{
  register int loop;
  register unsigned char status;

  do {
    loop = 0;
    if ( (status = inb( snd_pcm_status_reg )) & 0x01 ) {
      snd_ad1848_interrupt( snd_pcm, status );
      loop++;
    }
  } while ( loop );
}

#ifdef MOZART_SB_MIDI
static void snd_mozart_interrupt1( int irq, void *dev_id, struct pt_regs *regs )
{
}
#endif

static int snd_mozart_resources( void )
{
  static int possible_irqs[] = { 9, 10, 11, 7, -1 };
#ifdef MOZART_SB_MIDI
  static int possible_irqs1[] = { 11, 5, 7, -1 };
#endif
  static int possible_dmas[] = { 1, 3, 0, -1 };

  if ( (snd_irqnum = snd_register_interrupt( snd_card, "Mozart", snd_irq, SND_IRQ_TYPE_ISA, snd_mozart_interrupt, NULL, possible_irqs )) < 0 )
    return snd_irqnum;
#ifdef MOZART_SB_MIDI
  if ( (snd_irq1 >= 5 && snd_irq1 <= 11) || snd_irq1 == SND_AUTO_IRQ ) {
    if ( (snd_irq1num = snd_register_interrupt( snd_card, "Mozart", snd_irq1, SND_IRQ_TYPE_ISA, snd_mozart_interrupt1, NULL, possible_irqs1 )) < 0 )
      return snd_irq1num;
  } else {
    snd_irq1num = SND_IRQ_DISABLE;
  }
#endif
  if ( (snd_dma1num = snd_register_dma_channel( snd_card, "Mozart", snd_dma1, SND_DMA_TYPE_ISA, snd_dma1_size, possible_dmas )) < 0 ) {
    return snd_dma1num;
  }
  return 0;
}

static int snd_mozart_mixer( snd_kmixer_t *mixer )
{
  snd_kmixer_channel_t *channel1;
  
  channel1 = snd_mixer_find_channel( mixer, SND_MIXER_PRI_AUXA );
  channel1 -> hw.priority = SND_MIXER_PRI_CD;
  channel1 -> hw.ossdev = SND_MIXER_OSS_CD;
  strcpy( channel1 -> hw.name, SND_MIXER_ID_CD );
  snd_mixer_reorder_channel( mixer, channel1 );
  return 0;
}

static int snd_mozart_probe( void )
{
  int err;

  snd_card = snd_card_new( snd_index, snd_id,
  		           snd_mozart_use_inc, snd_mozart_use_dec );
  if ( !snd_card ) return -ENOMEM;
  snd_card -> type = SND_CARD_TYPE_MOZART;
  if ( snd_mozart_resources() < 0 ) {
    snd_card_free( snd_card );
    return -EBUSY;
  }
  if ( (err = snd_mozart_detect()) < 0 ) {
    snd_card_free( snd_card );
    return err;
  }

#ifdef MOZART_SB_MIDI
  if ( snd_irq1num != SND_IRQ_DISABLE ) {
    snd_sbpcm = snd_sbdsp_new_device( snd_card, snd_sbport, snd_irq1num, snd_dma1num, SND_DMA_DISABLE, SB_HW_PRO );
    printk( "snd_sbpcm = 0x%lx\n", (long)snd_sbpcm );
    if ( !snd_sbpcm ) goto __nodev;
  }
#endif

  snd_pcm_status_reg = snd_port + 4 + 2;
  snd_pcm = snd_ad1848_new_device( snd_card, snd_port + 4, snd_irqnum, snd_dma1num, AD1848_HW_DETECT );
  if ( !snd_pcm ) goto __nodev;

  snd_mixer = snd_ad1848_new_mixer( snd_pcm );
  if ( !snd_mixer ) goto __nodev;
  snd_mozart_mixer( snd_mixer );

  if ( snd_mixer_register( snd_mixer, 0 ) < 0 ) goto __nodev;
  if ( snd_pcm_register( snd_pcm, 0 ) < 0 ) {
    snd_mixer_unregister( snd_mixer ); snd_mixer = NULL;
    goto __nodev;
  }

  snd_enable_irq( snd_card, snd_irqnum );
#ifdef MOZART_SB_MIDI
  if ( snd_irq1num != SND_IRQ_DISABLE )
    snd_enable_irq( snd_card, snd_irq1num );
#endif

#if 0
  {
    ad1848_t *codec;
    codec = (ad1848_t *)snd_pcm -> private_data;
    snd_ad1848_debug( codec );
  }
#endif  

  strcpy( snd_card -> abbreviation, "OAK Mozart" );
  strcpy( snd_card -> shortname, "OAK Mozart" );
  sprintf( snd_card -> longname, "OAK Mozart at 0x%x, irq %i, dma %i",
  	snd_port,
  	snd_card -> irqs[ snd_irqnum ] -> irq,
  	snd_card -> dmas[ snd_dma1num ] -> dma );

  if ( !snd_card_register( snd_card ) )
    return 0;

  return -ENOMEM;
  
  __nodev:
  if ( snd_pcm ) snd_pcm_free( snd_pcm );
#ifdef MOZART_SB_MIDI
  if ( snd_sbpcm ) snd_pcm_free( snd_sbpcm );
#endif
  snd_card_free( snd_card );
  return -ENXIO;
}

int init_module( void )
{
  int err;

  if ( (err = snd_mozart_probe()) < 0 ) {
    snd_printk( "Mozart soundcard not found or device busy\n" );
    return err;
  }
  return 0;
}

void cleanup_module( void )
{
  if ( !snd_card ) return;
  snd_card_unregister( snd_card );
  if ( snd_mixer ) snd_mixer_unregister( snd_mixer );
  if ( snd_pcm ) snd_pcm_unregister( snd_pcm );
#ifdef MOZART_SB_MIDI
  if ( snd_sbpcm ) snd_pcm_free( snd_sbpcm );
#endif
  snd_card_free( snd_card );
}
