/*
 *  Driver for Yamaha Y701 chip based soundcards
 *
 *  Ivan Popov <pin@math.chalmers.se>, Oct 1998
 *  introduced this support using code developed by Jaroslav Kysela.
 *
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *
 *  Driver supports only one soundcard per system.
 *
 *  Driver is initializing WSS and SB modes and currently using WSS mode only.
 *  (it is possible to load SB8 driver separately)
 *
 *  !!! Does not allocate irq/dma for MPU and SB!
 *  !!! Does not check for availability either!
 *  !!! #ifdef SB_HERE is very incomplete!
 *
 *   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 "sb.h"
#include "ad1848.h"
#include "initval.h"

#if 0
#define Y701_DEBUG
#endif

/*
 *
 */

int snd_index = SND_DEFAULT_IDX1;               /* Index 1-MAX */
char *snd_id = SND_DEFAULT_STR1;                /* ID for this card */
int snd_port1 = SND_DEFAULT_PORT1;              /* WSS: 0x530,0xe80,0xf40,0x604 */
int snd_port2 = SND_DEFAULT_PORT1;              /* MPU: 0x330,0x332,0x334,0x300 */
int snd_port3 = SND_DEFAULT_PORT1;              /* SB: 0x220,0x240 */
int snd_irq1 = SND_DEFAULT_IRQ1;                /* WSS: 7,9,10,11 */
int snd_irq2 = SND_DEFAULT_IRQ1;                /* MPU: 5,7,9,10 */
int snd_irq3 = SND_DEFAULT_IRQ1;                /* SB: 5,7,9,10,11 */
int snd_dma1  = SND_DEFAULT_DMA1;               /* WSS: 0,1,3 */
int snd_dma2 = SND_DEFAULT_DMA1;                /* WSS: 1 0 0 */
int snd_dma3 = SND_DEFAULT_DMA1;                /* SB: 0,1,3 */
int snd_dma_size = SND_DEFAULT_DMA_SIZE1;       /* 8,16,32,64 */
int snd_dma3_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 Y701 soundcard." );
MODULE_PARM( snd_id, "s" );
MODULE_PARM_DESC( snd_id, "ID string for Y701 soundcard." );
MODULE_PARM( snd_port1, "i" );
MODULE_PARM_DESC( snd_port1, "WSS port # for Y701 driver." );
MODULE_PARM( snd_port2, "i" );
MODULE_PARM_DESC( snd_port2, "MPU port # for Y701 driver." );
MODULE_PARM( snd_port3, "i" );
MODULE_PARM_DESC( snd_port3, "SB port # for Y701 driver." );
MODULE_PARM( snd_irq1, "i" );
MODULE_PARM_DESC( snd_irq1, "WSS IRQ # for Y701 driver." );
MODULE_PARM( snd_irq2, "i" );
MODULE_PARM_DESC( snd_irq2, "Midi IRQ # for Y701 driver." );
MODULE_PARM( snd_irq3, "i" );
MODULE_PARM_DESC( snd_irq3, "SB IRQ # for Y701 driver." );
MODULE_PARM( snd_dma1, "i" );
MODULE_PARM_DESC( snd_dma1, "WSS DMA1 # for Y701 driver." );
MODULE_PARM( snd_dma2, "i" );
MODULE_PARM_DESC( snd_dma2, "WSS DMA2 # for Y701 driver." );
MODULE_PARM( snd_dma3, "i" );
MODULE_PARM_DESC( snd_dma3, "SB DMA # for Y701 driver." );
MODULE_PARM( snd_dma_size, "i" );
MODULE_PARM_DESC( snd_dma_size, "WSS DMA size in kB for Y701 driver." );
MODULE_PARM( snd_dma3_size, "i" );
MODULE_PARM_DESC( snd_dma3_size, "SB DMA size in kB for Y701 driver." );
#endif

/*
 *
 */

#define Y701_REG  0x0f86
#define Y701_DATA 0x0f87
#define Y701_PASS 0x1d

/*
 *
 */

static int snd_irq1num = SND_IRQ_DISABLE;
static int snd_irq2num = SND_IRQ_DISABLE;
static int snd_irq3num = SND_IRQ_DISABLE;
static int snd_dma1num = SND_DMA_DISABLE;
static int snd_dma2num = SND_DMA_DISABLE;
static int snd_dma3num = SND_DMA_DISABLE;
static snd_card_t *snd_card = NULL;

static snd_pcm_t *snd_pcm = NULL;
#ifdef SB_HERE
static snd_pcm_t *snd_sbpcm = NULL;
#endif

static snd_kmixer_t *snd_mixer = NULL;
#ifdef SB_HERE
static snd_kmixer_t *snd_sbmixer = NULL;
#endif

static unsigned short snd_pcm_status_reg = 0;

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

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

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

  snd_cli( &flags );
  outb( Y701_PASS, Y701_REG );
  outb(     index, Y701_REG );
  result = inb( Y701_DATA );
  snd_sti( &flags );
#ifdef Y701_DEBUG
  snd_printk( "y701_read( %i ) == 0x%x\n", index, result );
#endif
  return result;
}

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

#ifdef Y701_DEBUG
  snd_printk( "y701_write( %i, 0x%x )\n", index, val );
#endif
  snd_cli( &flags );
  outb( Y701_PASS, Y701_REG );
  outb(     index, Y701_REG );
  outb( val, Y701_DATA );
  snd_sti( &flags );
}

static int snd_y701_detect( void )
{
  static int possible_ports1[] = { 0x530, 0xe80, 0xf40, 0x604 };
  static int possible_ports2[] = { 0x330, 0x332, 0x334, 0x300 };
  static int possible_ports3[] = { 0x220, 0x240 };
  static int interrupt_bits1[] = { -1, -1, -1, -1, -1, -1, -1, 0x08, -1, 0x10, 0x18, 0x20, -1, -1, -1, -1 };
  static int interrupt_bits2[] = { -1, -1, -1, -1, -1, 0x04, -1, 0x08, -1, 0x0c, 0x10, -1, -1, -1, -1, -1 };
  static int interrupt_bits3[] = { -1, -1, -1, -1, -1, 0x04, -1, 0x08, -1, 0x0c, 0x10, 0x14, -1, -1, -1, -1 };
  static int dma_bits[] = { 1, 2, 0, 3 };
  static int dma_bits3[] = { 1, 2, 0, 3 };
  int tmp, tmp1, portsel;

  if ( snd_register_ioport( snd_card, Y701_REG, 2, "Y701 - control" ) < 0 )
    return -EBUSY;

  if ( snd_port1 == SND_AUTO_PORT ) {
    for ( portsel = 0; portsel < 4; portsel++ ) {
      if ( snd_register_ioport( snd_card, snd_port1 = possible_ports1[ portsel ], 8, "Y701 - WSS" ) >= 0 ) {
        break;
      }
    }
    if ( portsel >= 4 ) {
      snd_unregister_ioports( snd_card );
      return -EBUSY;
    }
  } else {
    for ( portsel = 0; portsel < 4; portsel++ )
      if ( possible_ports1[ portsel ] == snd_port1 ) break;
    if ( portsel >= 4 ) return -EINVAL;
    if ( snd_register_ioport( snd_card, snd_port1, 8, "Y701 - WSS" ) < 0 ) {
      snd_unregister_ioports( snd_card );
      return -EBUSY;
    }
  }
  /* try detect Y701 chip */
  if ( (tmp = snd_y701_read( 1 )) == 0xff ) {
    snd_printdd( "y701: reg 1 == 0xff\n" );
    snd_unregister_ioports( snd_card );
    return -ENODEV;
  }

  if ( (tmp1 = inb( Y701_DATA )) == tmp ) {
    snd_printdd( "y701: reg 1: tmp1 == tmp == 0x%x\n", tmp );
    snd_unregister_ioports( snd_card );
    return -ENODEV;
  }

  snd_y701_write( 1, tmp ^ 0x80 );
  tmp1 = snd_y701_read( 1 );
  snd_y701_write( 1, tmp );
  if ( (tmp ^ 0x80) != tmp1 ) {
    snd_printdd( "y701: reg 1: invert failed\n" );
    snd_unregister_ioports( snd_card );
    return -ENODEV;
  }
#if 0
  /* write map... */
  printk( "y701: map [0x00]: " );
  for ( tmp = 0; tmp < 3; tmp++ ) {
    printk( "%02x:", snd_y701_read( tmp ) );
  }
  printk( "\n" );
#endif
  /* ok.. Y701 chip found.. */
  snd_y701_write( 1, 0x00 ); /* disable WSS */
  snd_y701_write( 2, 0x00 ); /* disable SB */
  snd_y701_write( 3, 0x00 ); /* disable MPU */

  snd_y701_write( 1, 0x24 | (portsel << 3) ); /* enable WSS */

  /* initialize IRQ and DMA for WSS codec */
  tmp = interrupt_bits1[ snd_card -> irqs[ snd_irq1num ] -> irq ];
  if ( tmp < 0 ) return -EINVAL;
  outb( tmp | 0x40, snd_port1 );
  tmp1 = dma_bits[ snd_card -> dmas[ snd_dma1num ] -> dma ];
  if( snd_dma2num != SND_DMA_DISABLE )
     tmp1 |= 0x04; /* Enable capture DMA */
  outb( tmp | tmp1, snd_port1 );

  /* initialize MPU */
  if ( snd_irq2num != SND_IRQ_DISABLE ) {
#ifdef SB_HERE
    if ( snd_port2 == SND_AUTO_PORT ) {
      for ( portsel = 0; portsel < 4; portsel++ ) {
        if ( snd_register_ioport( snd_card, snd_port2 = possible_ports2[ portsel ], 2, "Y701 - MPU" ) >= 0 ) {
          break;
        }
      }
      if ( portsel >= 4 ) {
        snd_unregister_ioports( snd_card );
        return -EBUSY;
      }
    } else {
#endif
      for ( portsel = 0; portsel < 4; portsel++ )
        if ( possible_ports2[ portsel ] == snd_port2 ) break;
      if ( portsel >= 4 ) return -EINVAL;
#ifdef SB_HERE
      if ( snd_register_ioport( snd_card, snd_port2, 2, "Y701 - MPU" ) < 0 ) {
        snd_unregister_ioports( snd_card );
        return -EBUSY;
      }
    }
#endif
    tmp1 = portsel << 5; /* port selection */
#ifdef SB_HERE
    tmp = interrupt_bits2[ snd_card -> irqs[ snd_irq2num ] -> irq ];
#else
    tmp = interrupt_bits2[ snd_irq2 ];
#endif
    if ( tmp < 0 ) return -EINVAL;
    tmp1 |= 0x80|tmp|0x02; /* enable MPU+OPL3 */
  } else { /* no MPU */
    tmp1 = 0x00; /* disable MPU+OPL3 - I don't know how to do separately */
  }
  /* try to allocate OPL3 and game adapter addresses */
#ifdef SB_HERE
  if ( snd_register_ioport( snd_card, 0x388, 2, "Y701 - OPL3" ) < 0 ) {
    snd_unregister_ioports( snd_card );
    return -EBUSY;
  }
#endif
  if ( snd_register_ioport( snd_card, 0x201, 2, "Y701 - Game adapter" ) < 0 ) {
    ; /* no game port address available */
  } else {
    tmp1 |= 0x01; /* enable game port */
  }
  snd_y701_write( 3, tmp1 ); /* enable/disable MPU+OPL3, game adapter */

  /* initialize SB */
  if ( snd_irq3num != SND_IRQ_DISABLE ) {
#ifdef SB_HERE
    if ( snd_port3 == SND_AUTO_PORT ) {
      for ( portsel = 0; portsel < 2; portsel++ ) {
        if ( snd_register_ioport( snd_card, snd_port3 = possible_ports3[ portsel ], 16, "Y701 - SB" ) >= 0 ) {
          break;
        }
      }
      if ( portsel >= 2 ) {
        snd_unregister_ioports( snd_card );
        return -EBUSY;
      }
    } else {
#endif
      for ( portsel = 0; portsel < 2; portsel++ )
        if ( possible_ports3[ portsel ] == snd_port3 ) break;
      if ( portsel >= 2 ) return -EINVAL;
#ifdef SB_HERE
      if ( snd_register_ioport( snd_card, snd_port3, 16, "Y701 - SB" ) < 0 ) {
        snd_unregister_ioports( snd_card );
        return -EBUSY;
      }
    }
#endif
    tmp1 = portsel << 5; /* port selection */
#ifdef SB_HERE
    tmp = interrupt_bits3[ snd_card -> irqs[ snd_irq3num ] -> irq ];
#else
    tmp = interrupt_bits3[ snd_irq3 ];
#endif
    if ( tmp < 0 ) return -EINVAL;
    tmp1 |= tmp; /* port + interrupt */
#ifdef SB_HERE
    tmp = dma_bits3[ snd_card -> dmas[ snd_dma3num ] -> dma ];
#else
    tmp = dma_bits3[ snd_dma3 ]; /* danger! no checking here, but in resources */
#endif
    if ( tmp < 0 ) tmp = 0; /* no dma */
    tmp1 |= tmp; /* port + interrupt + dma */
    snd_y701_write( 2, 0x40|tmp1 ); /* enable SB */
  }
  return 0;
}

static void snd_y701_interrupt1( 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 SB_HERE
static void snd_y701_interrupt2( int irq, void *dev_id, struct pt_regs *regs )
{
}
#endif

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

static int snd_y701_resources( void )
{
  static int possible_irqs1[] = { 9, 10, 11, 7, -1 };
  static int possible_irqs2[] = { 9, 10, 5, 7, -1 };
  static int possible_irqs3[] = { 9, 10, 11, 5, 7, -1 };
  static int possible_dmas[] = { 1, 3, 0, -1 };
  static int possible_dmas3[] = { 1, 3, 0, -1 };

  if ( (snd_irq1num = snd_register_interrupt( snd_card, "Y701", snd_irq1, SND_IRQ_TYPE_ISA, snd_y701_interrupt1, NULL, possible_irqs1 )) < 0 )
    return snd_irq1num;

  if( snd_irq2 >= 0 ){
#ifdef SB_HERE
    if( (snd_irq2num = snd_register_interrupt( snd_card, "Y701", snd_irq2, SND_IRQ_TYPE_ISA, snd_y701_interrupt2, NULL, possible_irqs2 )) < 0 )
      return snd_irq2num;
#else
    snd_irq2num = snd_irq2;
#endif
  } else snd_irq2num = SND_IRQ_DISABLE;

  if( snd_irq3 >= 0 ){
#ifdef SB_HERE
    if( (snd_irq3num = snd_register_interrupt( snd_card, "Y701", snd_irq3, SND_IRQ_TYPE_ISA, snd_y701_interrupt3, NULL, possible_irqs3 )) < 0 )
      return snd_irq3num;
#else
    snd_irq3num = snd_irq3;
#endif
  } else snd_irq3num = SND_IRQ_DISABLE;

  if ( (snd_dma1num = snd_register_dma_channel( snd_card, "Y701", snd_dma1, SND_DMA_TYPE_ISA, snd_dma_size, possible_dmas )) < 0 )
    return snd_dma1num;

#if 0
  /* hack to provide for dual dma */
  if( (snd_dma2==0 && (snd_dma1 == 1 || snd_dma1==3))
   || (snd_dma2==1 && snd_dma1==0) ){
    if ( (snd_dma2num = snd_register_dma_channel( snd_card, "Y701", snd_dma2, SND_DMA_TYPE_ISA, snd_dma_size, possible_dmas2 )) < 0 ) {
      return snd_dma2num;
    }
  } else
#endif
         snd_dma2num = SND_DMA_DISABLE;

#ifdef SB_HERE
  if ( (snd_dma3num = snd_register_dma_channel( snd_card, "Y701", snd_dma3, SND_DMA_TYPE_ISA, snd_dma3_size, possible_dmas3 )) < 0 )
    return snd_dma3num;
#else
  if ( snd_dma3 < 0 ) snd_irq3num = SND_IRQ_DISABLE; /* flag to avoid initializing wrong dma */
  else                ; /* snd_dma3num is not needed anyway */
#endif

  return 0;
}

static int snd_y701_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_y701_probe( void )
{
  int err;

  snd_card = snd_card_new( snd_index, snd_id,
                           snd_y701_use_inc, snd_y701_use_dec );
  if ( !snd_card ) return -ENOMEM;
  snd_card -> type = SND_CARD_TYPE_Y701;
  if ( snd_y701_resources() < 0 ) {
    snd_card_free( snd_card );
    return -EBUSY;
  }
  if ( (err = snd_y701_detect()) < 0 ) {
    snd_card_free( snd_card );
    return err;
  }

#ifdef SB_HERE
  if ( snd_irq3num != SND_IRQ_DISABLE ) {
    snd_sbpcm = snd_sbdsp_new_device( snd_card, snd_sbport, snd_irq3num, snd_dma3num, 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_port1 + 4 + 2;
  snd_pcm = snd_ad1848_new_device( snd_card, snd_port1 + 4, snd_irq1num, snd_dma1num, AD1848_HW_DETECT );
  if ( !snd_pcm ) goto __nodev;

  snd_mixer = snd_ad1848_new_mixer( snd_pcm );
  if ( !snd_mixer ) goto __nodev;
  snd_y701_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_irq1num );
#ifdef SB_HERE
  if ( snd_irq2num != SND_IRQ_DISABLE )
    snd_enable_irq( snd_card, snd_irq2num );
  if ( snd_irq3num != SND_IRQ_DISABLE )
    snd_enable_irq( snd_card, snd_irq3num );
#endif

  strcpy( snd_card -> abbreviation, "Yamaha Y701" );
  strcpy( snd_card -> shortname, "Yamaha Y701" );
  sprintf( snd_card -> longname, "Yamaha Y701 at 0x%x, irq %i, dma %i",
        snd_port1,
        snd_card -> irqs[ snd_irq1num ] -> 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 SB_HERE
  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_y701_probe()) < 0 ) {
    snd_printk( "Yamaha Y701 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 SB_HERE
  if ( snd_sbmixer ) snd_mixer_unregister( snd_sbmixer );
  if ( snd_sbpcm ) snd_pcm_free( snd_sbpcm );
#endif
  snd_card_free( snd_card );
}
