/*
 *  Copyright (c) 1994/95 by Jaroslav Kysela (Perex soft)
 */

#include "gus_dev.h"
#if 0
#include "/usr/src/linux/tools/version.h"	/* for old kernels */
#else
#include <linux/version.h>	/* here is new location of version.h */
#endif

#ifdef MODULE
char kernel_version[] = UTS_RELEASE;
#endif

#undef FTAPE_DMA		/* don't use this - obsolete */

#define DMA_BUFFERS_SIZE	( ( DMA_BUF_SIZE * 1024 ) - 64 )

static int gus_lseek( struct inode *inode, struct file *file, off_t offset, int orig )
{
  switch ( MINOR( inode -> i_rdev ) ) {
    case GUS_MINOR_GSYNTH:
    case GUS_MINOR_MIXER:
    case GUS_MINOR_PCM_8: 
    case GUS_MINOR_PCM_16:
    case GUS_MINOR_AUDIO:	return -EIO;
#ifdef VOXWARE_SEQUENCER
    case GUS_MINOR_SEQUENCER:
    case GUS_MINOR_SEQUENCER2:
      return gus_lseek_sequencer( MINOR( inode -> i_rdev ), offset, orig );
    case GUS_MINOR_SNDSTAT:	return -EIO;
#endif
#ifndef PROC_SUPPORT
    case GUS_MINOR_INFO:	return -EIO;
#endif
  }
  return -ENODEV;
}

static int gus_read( struct inode *inode, struct file *file, char *buf, int count )
{
  switch ( MINOR( inode -> i_rdev ) ) {
    case GUS_MINOR_GSYNTH:	return gus_read_gf1( buf, count );
    case GUS_MINOR_MIXER:	return -EIO;
    case GUS_MINOR_PCM_8: 
    case GUS_MINOR_PCM_16:
    case GUS_MINOR_AUDIO:	
      return gus_read_pcm( MINOR( inode -> i_rdev ), buf, count );
#ifdef VOXWARE_SEQUENCER
    case GUS_MINOR_SEQUENCER:
    case GUS_MINOR_SEQUENCER2:
      return gus_read_sequencer( MINOR( inode -> i_rdev ), buf, count );
    case GUS_MINOR_SNDSTAT:
      return gus_read_sndstat( buf, (off_t *)&file -> f_pos, count );
#endif
#ifndef PROC_SUPPORT
    case GUS_MINOR_INFO:
      return gus_read_info( buf, (off_t *)&file -> f_pos, count );
#endif
  }
  return -ENODEV;
}

static int gus_write( struct inode *inode, struct file *file, char *buf, int count )
{
  switch ( MINOR( inode -> i_rdev ) ) {
    case GUS_MINOR_GSYNTH:	return gus_write_gf1( buf, count );
    case GUS_MINOR_MIXER:	return -EIO;
    case GUS_MINOR_PCM_8: 
    case GUS_MINOR_PCM_16:
    case GUS_MINOR_AUDIO:	
      return gus_write_pcm( MINOR( inode -> i_rdev ), buf, count );
#ifdef VOXWARE_SEQUENCER
    case GUS_MINOR_SEQUENCER:
    case GUS_MINOR_SEQUENCER2:
      return gus_write_sequencer( MINOR( inode -> i_rdev ), buf, count );
    case GUS_MINOR_SNDSTAT:	return -EIO;
#endif
#ifndef PROC_SUPPORT
    case GUS_MINOR_INFO:	return -EIO;
#endif
  } 
  return -ENODEV;
}

static int gus_open( struct inode *inode, struct file *file )
{
  switch ( MINOR( inode -> i_rdev ) ) {
    case GUS_MINOR_GSYNTH:	return gus_open_gf1();
    case GUS_MINOR_MIXER:	return gus_open_mixer();
    case GUS_MINOR_PCM_8: 
    case GUS_MINOR_PCM_16:
    case GUS_MINOR_AUDIO:	
      return gus_open_pcm( MINOR( inode -> i_rdev ), file -> f_flags );
#ifdef VOXWARE_SEQUENCER
    case GUS_MINOR_SEQUENCER:
    case GUS_MINOR_SEQUENCER2:
      return gus_open_sequencer( MINOR( inode -> i_rdev ), file -> f_flags );
    case GUS_MINOR_SNDSTAT:	return gus_open_sndstat();
#endif
#ifndef PROC_SUPPORT
    case GUS_MINOR_INFO:	return gus_open_info();
#endif
  }
  return -ENODEV;
}

static void gus_release(struct inode * inode, struct file * file)
{
  switch ( MINOR( inode -> i_rdev ) ) {
    case GUS_MINOR_GSYNTH:	gus_release_gf1();	break;
    case GUS_MINOR_MIXER:	gus_release_mixer(); 	break;
    case GUS_MINOR_PCM_8: 
    case GUS_MINOR_PCM_16:
    case GUS_MINOR_AUDIO:	
      gus_release_pcm( MINOR( inode -> i_rdev ), file -> f_flags );
      break;
#ifdef VOXWARE_SEQUENCER
    case GUS_MINOR_SEQUENCER:
    case GUS_MINOR_SEQUENCER2:
      gus_release_sequencer( MINOR( inode -> i_rdev ), file -> f_flags );
      break;
    case GUS_MINOR_SNDSTAT:	gus_release_sndstat();	break;
#endif
#ifndef PROC_SUPPORT
    case GUS_MINOR_INFO:	gus_release_info();	break;
#endif
  }
}

static int gus_select( struct inode *inode, struct file *file, 
                       int sel_type, select_table *wait )
{
  switch ( MINOR( inode -> i_rdev ) ) {
    case GUS_MINOR_GSYNTH:
    case GUS_MINOR_MIXER:
    case GUS_MINOR_PCM_8: 
    case GUS_MINOR_PCM_16:
    case GUS_MINOR_AUDIO:	return -EIO;
#ifdef VOXWARE_SEQUENCER
    case GUS_MINOR_SEQUENCER:
    case GUS_MINOR_SEQUENCER2:
      return gus_select_sequencer( MINOR( inode -> i_rdev ), file -> f_flags );
    case GUS_MINOR_SNDSTAT:	return -EIO;
#endif
#ifndef PROC_SUPPORT
    case GUS_MINOR_INFO:	return -EIO;
#endif
  }
  return -ENODEV;
}                       

static int gus_ioctl( struct inode *inode, struct file *file,
		      unsigned int cmd, unsigned long arg )
{
  switch ( MINOR( inode -> i_rdev ) ) {
    case GUS_MINOR_GSYNTH: return gus_ioctl_gf1( cmd, arg );
    case GUS_MINOR_MIXER: return gus_ioctl_mixer( cmd, arg );
    case GUS_MINOR_PCM_8:
    case GUS_MINOR_PCM_16: 
    case GUS_MINOR_AUDIO: 
      return gus_ioctl_pcm( MINOR( inode -> i_rdev ), cmd, arg );
#ifdef VOXWARE_SEQUENCER
    case GUS_MINOR_SEQUENCER:
    case GUS_MINOR_SEQUENCER2:
      return gus_ioctl_sequencer( MINOR( inode -> i_rdev ), cmd, arg );
    case GUS_MINOR_SNDSTAT: return -EIO;
#endif
#ifndef PROC_SUPPORT
    case GUS_MINOR_INFO: return -EIO;
#endif
  }
  return -ENODEV;
}

static struct file_operations gus_fops = {
  gus_lseek,	/* gus_lseek */
  gus_read,	/* gus_read */
  gus_write,	/* gus_write */
  NULL,		/* gus_readdir */
  gus_select,	/* gus_select */
  gus_ioctl,	/* gus_ioctl */
  NULL,		/* gus_mmap */
  gus_open,	/* gus_open */
  gus_release	/* gus_release */
};

int gus_ioctl_out( int *addr, int value )
{
  int err;
  
  if ( ( err = verify_area( VERIFY_WRITE, addr, sizeof( int ) ) ) != 0 )
    return err;
  put_fs_int( value, addr );
  return 0;
}

#ifdef MODULE

int init_module( void )
{
#ifdef FTAPE_DMA
  extern char *ftape_big_buffer;
#endif

  PRINTK( "gus: " VERSION_AND_COPYRIGHT "\n" );
  if ( check_region( gus_port, 16 ) )
    PRINTK( "gus: unable to grab io ports for region 0x%x-0x%x\n", gus_port, gus_port + 16 - 1 );
  if ( check_region( gus_port + 0x100, 16 ) )
    PRINTK( "gus: unable to grab io ports for region 0x%x-0x%x\n", gus_port + 0x100, gus_port + 0x100 + 16 - 1 );
  if ( check_region( gus_port + ( 0x726 - 0x220 ), 1 ) )
    PRINTK( "gus: unable to grab io port 0x%x\n", gus_port + ( 0x726 - 0x220 ) );
  if ( gus_init() ) return -ENODEV;
  if ( request_irq( gus_irq, gus_interrupt, SA_INTERRUPT, "gus" ) != 0 )
    {
      PRINTK( "unable to grab IRQ %d for gus driver\n", (int)gus_irq );
      return -EIO;
    }
  if ( request_dma( gus_dma1, "gus (dma1)" ) != 0 )
    {
      free_irq( gus_irq );
      PRINTK( "unable to grab DMA1 %d for gus driver\n", (int)gus_dma1 );
      return -EIO;
    }
  if ( gus_dma2 < 0 ) gus_dma2 = gus_dma1;	/* same */
  if ( gus_dma2 != gus_dma1 && request_dma( gus_dma2, "gus (dma2)" ) != 0 )
    {
      free_irq( gus_irq );
      free_dma( gus_dma1 );
      PRINTK( "unable to grab DMA2 %d for gus driver\n", (int)gus_dma2 );
      return -EIO;
    }
#ifdef FTAPE_DMA
  dma1_buf = ftape_big_buffer;
  dma1_buf_size = 0x4000;
  dma2_buf = ftape_big_buffer + dma1_buf_size;
  dma2_buf_size = 0x4000;
#else
  dma1_buf_size = dma2_buf_size = DMA_BUFFERS_SIZE;
  if ( ( dma1_buf = kmalloc( dma1_buf_size, GFP_DMA | GFP_KERNEL ) ) == NULL ||
       ( gus_dma1 != gus_dma2 &&
       ( dma2_buf = kmalloc( dma2_buf_size, GFP_DMA | GFP_KERNEL ) ) == NULL ) )
    {
      if ( dma1_buf ) kfree_s( dma1_buf, dma1_buf_size );
      if ( gus_dma1 != gus_dma2 && dma2_buf ) kfree_s( dma2_buf, dma2_buf_size );
      free_irq( gus_irq );
      free_dma( gus_dma1 );
      if ( gus_dma1 != gus_dma2 ) free_dma( gus_dma2 );
      PRINTK( "kmalloc error\n" );
      return -EIO;
    }
  if ( gus_dma1 == gus_dma2 ) dma2_buf = dma1_buf;
#endif
  if ( register_chrdev(GUS_MAJOR,"gus",&gus_fops) ) 
    {
      free_irq( gus_irq );
      free_dma( gus_dma1 );
      if ( gus_dma2 != gus_dma1 ) free_dma( gus_dma2 );
#ifndef FTAPE_DMA
      kfree_s( dma1_buf, dma1_buf_size );
      if ( gus_dma1 != gus_dma2 ) kfree_s( dma2_buf, dma2_buf_size );      
#endif
      PRINTK("unable to get major %d for gus device\n", GUS_MAJOR);
      return -EIO;
    }
#ifdef FTAPE_DMA
  if ( register_chrdev(FTAPE_MAJOR, "gus-ftape-lock", &gus_fops ) )
    {
      free_irq( gus_irq );
      free_dma( gus_dma1 );
      if ( gus_dma2 != gus_dma1 ) free_dma( gus_dma2 );
      PRINTK("unable to get major %d for ftape lock device\n", FTAPE_MAJOR);
      if ( unregister_chrdev( GUS_MAJOR, "gus" ) != 0 )
        PRINTK;( "gus_cleanup: failed ftape" );
      return -EIO;
    }
#endif
  enable_irq( gus_irq );
  request_region( gus_port, 16,                         "gus (region 1)" );
  request_region( gus_port + 0x100, 16,                 "gus (region 2)" );
  request_region( gus_port + ( 0x726 - 0x220 ), 1,      "gus (region 3)" );
#ifdef PROC_SUPPORT
  gus_proc_file_init();
#endif
  return 0;
}

void cleanup_module( void )
{
#ifdef PROC_SUPPORT
  gus_proc_file_done();
#endif
  disable_irq( gus_irq );
  release_region( gus_port, 16 );
  release_region( gus_port + 0x100, 16 );
  release_region( gus_port + ( 0x726 - 0x220 ), 1 );
  free_irq( gus_irq );
  free_dma( gus_dma1 );
  if ( gus_dma2 != gus_dma1 ) free_dma( gus_dma2 );
#ifndef FTAPE_DMA
  kfree_s( dma1_buf, dma1_buf_size );
  if ( gus_dma2 != gus_dma1 ) kfree_s( dma2_buf, dma2_buf_size );
#endif
  if ( unregister_chrdev( GUS_MAJOR, "gus" ) != 0 )
    PRINTK( "gus_cleanup: failed" );
#ifdef FTAPE_DMA
  if ( unregister_chrdev( FTAPE_MAJOR, "gus-ftape-lock" ) != 0 )
    PRINTK( "gus_cleanup: failed ftape" );
#endif
}

#endif MODULE
