/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for control of GF1 chip (PCM things)
 *
 *  Input samples must be separated for each voices for GF1 chip.
 *  Input:  L1R1L1R1L1R1L2R2L2R2L2R2 (user space or mmaped DMA buffer)
 *  Output: L1L1L1L2L2L2R1R1R1R2R2R2 (DMA buffer or hidden DMA buffer)
 *  Number in this case means fragment.
 *  InterWave chips supports interleaved DMA, but this feature isn't used in
 *  this code.
 *  
 *  This code emulates autoinit DMA transfer for playback, recording by GF1
 *  chip doesn't support autoinit DMA.
 *
 *  MMAPed access is emulated for OSS, but is a little bit ugly :-(((((
 *
 *
 *   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__
#include "driver.h"
#include "pcm.h"
#include "ulaw.h"
#include "mixer.h"
#include "gus.h"
#include "gus_tables.h"

/* position */

#define SND_POS_NONE		0xffffffff

/* playback flags */

#define SND_GF1_PCM_PFLG_NONE  	0x0000
#define SND_GF1_PCM_PFLG_UP	0x0001	/* trigger up */
#define SND_GF1_PCM_PFLG_DMA	0x0004	/* DMA in progress */
#define SND_GF1_PCM_PFLG_ACTIVE	0x0008	/* voice is playing */

/* record flags */

#define RFLG_NONE		0x0000
#define RFLG_USED		0x0001
#define RFLG_STOP		0x0002

/* some macros */

#define SND_GF1_PCM_VOICES()	( gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].voices )
#define SND_GF1_PCM_VOICE_MIN()	( gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].min )
#define SND_GF1_PCM_VOICE_MAX()	( gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].max )
#define SND_GF1_PCM_RVOICES()	( gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].rvoices )

#if defined( __i386__ ) && 1

#if 0

extern inline void translate_bytes( unsigned char *table, 
                                    unsigned char *buff, 
                                    unsigned int count )
{
  __asm__ ("cld\n"
           "1:\tlodsb\n"
           "\txlatb\n"
           "\tstosb\n"
           "\tloop 1b\n":
           :"b" ((long) table), "c" (count), "D" ((long) buff), "S" ((long) buff)
           :"bx", "cx", "di", "si", "ax", "memory");
}

#endif

extern inline void divide_bytes( unsigned char *dest1,
			         unsigned char *dest2,
			         unsigned char *src,
			         unsigned int count )
{
  __asm__( "cld\n"
  	   "1:\tlodsw\n"
  	   "\tstosb\n"
  	   "\txchgb %%ah,%%al\n"
  	   "\txchgl %%edi,%%ebx\n"
  	   "\tstosb\n"
  	   "\txchgl %%edi,%%ebx\n"
  	   "\tloop 1b\n":
  	   :"c" (count), "S" ((long)src), "D" ((long)dest1), "b" (dest2)
  	   :"bx", "cx", "di", "si", "ax", "memory");
}

extern inline void divide_words( unsigned short *dest1,
			         unsigned short *dest2,
			         unsigned short *src,
			         unsigned int count )
{
  __asm__( "cld\n"
  	   "1:\tlodsw\n"
  	   "\tstosw\n"
  	   "\tlodsw\n"
  	   "\txchgl %%edi,%%ebx\n"
  	   "\tstosw\n"
  	   "\txchgl %%edi,%%ebx\n"
  	   "\tloop 1b\n":
  	   :"c" (count), "S" ((long)src), "D" ((long)dest1), "b" (dest2)
  	   :"bx", "cx", "di", "si", "ax", "memory");
}

#else

#if 0

static void translate_bytes( unsigned char *table,
			     unsigned char *buff,
			     unsigned int count )
{
  while ( count-- > 0 )
    {
      *buff = table[ *buff ];
      buff++;
    }
}

#endif

static void divide_bytes( unsigned char *dest1,
			  unsigned char *dest2,
		   	  unsigned char *src,
		  	  unsigned int count )
{
  while ( count-- > 0 ) {
    *dest1++ = *src++;
    *dest2++ = *src++;
  }
}

static void divide_words( unsigned short *dest1,
		  	  unsigned short *dest2,
			  unsigned short *src,
			  unsigned int count )
{
  while ( count-- > 0 ) {
    *(((unsigned short *)dest1)++) = *(((unsigned short *)src)++);
    *(((unsigned short *)dest2)++) = *(((unsigned short *)src)++);
  }
}

#endif

/*
 *  Playback routines 
 */

static void snd_gf1_pcm_translate( unsigned int voices,
				   unsigned char *src,
				   unsigned char *dest,
				   unsigned int pos,
				   unsigned int count,
				   unsigned int total,
				   unsigned int mode,
				   int user_space )
{
  register int voice;
  register unsigned int pos1;
  unsigned int tmp;
  unsigned int count1;
  unsigned char buffer[ 512 ];
  unsigned char *bpos, *dest1, *dest2;

#if 0
  snd_printk( "pcm: voices = %i, src=0x%lx, dest=0x%lx, pos=0x%x, count=0x%x, total=0x%x\n", voices, (long)src, (long)dest, pos, count, total );
  if ( user_space && verify_area( VERIFY_READ, src, count ) )
    snd_printk( "Verify failed!!!\n" );
#endif
  tmp = voices * ( (mode & SND_PCM_MODE_16) ? 2 : 1 );
  if ( ( count % tmp ) != 0 ) {
    snd_printd( "sound: GF1 PCM problem - unaligned count (voices = %i, count = %i)\n", voices, count );
    return;
  }
  if ( ( pos % tmp ) != 0 ) {
    snd_printd( "sound: GF1 PCM problem - unaligned copy (voices = %i, pos = %i)\n", voices, pos );
    return;
  }
  pos /= voices;
  total /= voices;
#if 0
  printk( "pos = 0x%x, total = 0x%x\n", pos, total );
#endif
  while ( count > 0 ) {
    if ( user_space ) {
      count1 = count > sizeof( buffer ) ? sizeof( buffer ) : count;
      copy_from_user( buffer, src, count1 );
      src += count1;
      bpos = buffer;
      count -= count1;
    } else {
      count1 = count;
      bpos = src;
      count = 0;
    }

    /*
     *  Next part strongly needs optimalization...
     *  Optimalization for one and two voices is done (not for uLaw)...
     *  I don't have time for this... If you want - try rewrite this part...
     */
    if ( mode & SND_PCM_MODE_16 ) {
      if ( voices == 1 ) {
        memcpy( dest + pos, bpos, count1 );
        pos += count1;
      } else if ( voices == 2 ) {
        divide_words( (unsigned short *)(dest + pos),
                      (unsigned short *)(dest + pos + total),
                      (unsigned short *)bpos, count1 >> 2 );
        pos += count1 >> 1;
      } else {
        count1 >>= 1;
        while ( count1 > 0 ) {
          for ( voice = 0, pos1 = pos; voice < voices; voice++, pos1 += total )
            *(unsigned short *)&dest[ pos1 ] = *(((unsigned short *)bpos)++);
          pos += sizeof( unsigned short );
          count1 -= voices;
        }
      }
    } else  {
      if ( mode & SND_PCM_MODE_ULAW ) {
        if ( voices == 1 ) {
          dest1 = dest + pos;
          pos += count1;
          while ( count1-- > 0 )
            *dest1++ = snd_ulaw_dsp[ *bpos++ ];
        } else if ( voices == 2 ) {
          dest1 = dest + pos;
          dest2 = dest1 + total;
          pos += count1;
          count1 >>= 1;
          while ( count1-- > 0 ) {
            *dest1++ = snd_ulaw_dsp[ *bpos++ ];
            *dest2++ = snd_ulaw_dsp[ *bpos++ ];
          }
        } else {
          while ( count1 > 0 ) {
            for ( voice = 0, pos1 = pos; voice < voices; voice++, pos1 += total )
              dest[ pos1 ] = snd_ulaw_dsp[ *bpos++ ];
            pos++;
            count1 -= voices;
          }
        }
      } else {
        if ( voices == 1 ) {
          memcpy( dest + pos, bpos, count1 );
          pos += count1;
        } else if ( voices == 2 ) {
          pos += count1;
          divide_bytes( dest + pos, dest + pos + total, bpos, count1 >> 1 );
        } else {
          while ( count1 > 0 ) {
            for ( voice = 0, pos1 = pos; voice < voices; voice++, pos1 += total )
              dest[ pos1 ] = *bpos++;
            pos++;
            count1 -= voices;
          }
        }
      }
    }
  }
#if 0
  snd_printk( "translate end\n" );
#endif
}

static void snd_gf1_pcm_block_change( snd_pcm_t *pcm, snd_gus_card_t *gus, unsigned int offset, unsigned int count )
{
  unsigned int cmd;

#if 0
  snd_printk( "block change - offset = 0x%x, count = 0x%x\n", offset, count );
#endif
  if ( offset & 31 ) {
    count += count & 31;
    offset &= ~31;
  }
  if ( count < 32 ) count = 32;
  cmd = SND_GF1_DMA_IRQ;
  if ( pcm -> playback.mode & SND_PCM_MODE_U )
    cmd |= SND_GF1_DMA_UNSIGNED;
  if ( pcm -> playback.mode & SND_PCM_MODE_16 )
    cmd |= SND_GF1_DMA_16BIT;
  snd_gf1_dma_transfer_block( gus,
  			      gus -> gf1.pcm_memory + offset,
  			      pcm -> playback.buffer + offset,
  			      count,
  			      cmd );
}

static void snd_gf1_pcm_mmap_change( snd_pcm_t *pcm,
                                     snd_gus_card_t *gus,
                                     unsigned int offset,
                                     unsigned int poffset,
                                     unsigned int count )
{
  unsigned int cmd;

#if 0
  snd_printk( "block change - buffer = 0x%lx, offset = 0x%x, poffset = 0x%x, count = 0x%x\n", (long)gus -> gf1.pcm_buffer, offset, poffset, count );
#endif
  cmd = SND_GF1_DMA_IRQ;
  if ( pcm -> playback.mode & SND_PCM_MODE_U )
    cmd |= SND_GF1_DMA_UNSIGNED;
  if ( pcm -> playback.mode & SND_PCM_MODE_16 )
    cmd |= SND_GF1_DMA_16BIT;
  snd_gf1_dma_transfer_block( gus,
  			      gus -> gf1.pcm_memory + offset,
  			      gus -> gf1.pcm_buffer + poffset,
  			      count,
  			      cmd );
}

static unsigned short snd_gf1_pcm_volume( snd_gus_card_t *gus, int vol, int pan )
{
  unsigned long flags;
  
  snd_spin_lock( gus, pcm_volume_level, &flags );
  if ( vol > 127 ) vol = 127;
  vol = SND_GF1_VOLUME( vol );
  if ( pan < 64 )               /* left */
    vol += gus -> gf1.pcm_volume_level_left;
   else
    vol += gus -> gf1.pcm_volume_level_right;
  if ( vol > SND_GF1_MAX_VOLUME ) vol = SND_GF1_MAX_VOLUME;
  vol = SND_GF1_MAX_VOLUME - vol;
  snd_spin_unlock( gus, pcm_volume_level, &flags );
  return vol;
}

static void snd_gf1_pcm_trigger_up( snd_pcm_t *pcm, snd_gus_card_t *gus )
{
  unsigned long flags; 
  unsigned char voice_ctrl, ramp_ctrl;
  unsigned short rate;
  unsigned int tmp, curr, begin, end;
  unsigned short vol;
  unsigned char pan;
  unsigned int voice;

  snd_spin_lock( gus, playback, &flags );
  if ( gus -> gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE ) {
    snd_spin_unlock( gus, playback, &flags );
    return;
  }
  gus -> gf1.pcm_pflags |= SND_GF1_PCM_PFLG_ACTIVE;
  snd_spin_unlock( gus, playback, &flags );
  rate = snd_gf1_translate_freq( gus, pcm -> playback.real_rate << 1 );
  if ( gus -> gf1.pcm_pos != SND_POS_NONE ) {
    voice_ctrl = gus -> gf1.pcm_voice_ctrl;
    /* enable RAMP IRQ */
    ramp_ctrl = gus -> gf1.pcm_ramp_ctrl | 0x20;
  } else {
    /* enable WAVE IRQ */
    voice_ctrl = pcm -> playback.mode & SND_PCM_MODE_16 ? 0x24 : 0x20;
    /* enable RAMP IRQ + rollover */
    ramp_ctrl = 0x24;
  }
  tmp = SND_GF1_PCM_RVOICES();
  for ( voice = 0; voice < SND_GF1_PCM_VOICES(); voice++ )
    {
      begin = gus -> gf1.pcm_memory + voice * ( pcm -> playback.used_size / tmp );
      curr = begin + ( gus -> gf1.pcm_bpos * gus -> gf1.pcm_block_size ) / tmp;
      end = curr + ( gus -> gf1.pcm_block_size / tmp );
      if ( gus -> gf1.pcm_pos != SND_POS_NONE )	/* stored position */
        curr = begin + gus -> gf1.pcm_pos;
      end -= pcm -> playback.mode & SND_PCM_MODE_16 ? 2 : 1;
#if 0
      snd_printk( "init: curr=0x%x, begin=0x%x, end=0x%x, ctrl=0x%x, ramp=0x%x, rate=0x%x\n", curr, begin, end, voice_ctrl, ramp_ctrl, rate );
#endif
      if ( pcm -> playback.mode & SND_PCM_MODE_MULTI ) {
        vol = snd_gf1_pcm_volume( gus, gus -> gf1.pcm_volume[ voice ], pan = gus -> gf1.pcm_pan[ voice ] );
      } else {
        if ( pcm -> playback.voices == 2 ) pan = !voice ? 8 : 128 - 9; else pan = 64;
        vol = snd_gf1_pcm_volume( gus, 128, pan );
      }
      pan >>= 3;
      if ( pan > 15 ) pan = 15;
      snd_spin_lock( gus, reg, &flags );
      snd_gf1_select_voice( gus, voice + SND_GF1_PCM_VOICE_MIN() );
      snd_gf1_write8( gus, SND_GF1_VB_PAN, pan );
      snd_gf1_write16( gus, SND_GF1_VW_FREQUENCY, rate );
      snd_gf1_write_addr( gus, SND_GF1_VA_START, begin << 4, voice_ctrl & 4 );
      snd_gf1_write_addr( gus, SND_GF1_VA_END, end << 4, voice_ctrl & 4 );
      snd_gf1_write_addr( gus, SND_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4 );
      snd_gf1_write16( gus, SND_GF1_VW_VOLUME, SND_GF1_MIN_VOLUME << 4 );
      snd_gf1_write8( gus, SND_GF1_VB_VOLUME_RATE, 0x2f );
      snd_gf1_write8( gus, SND_GF1_VB_VOLUME_START, SND_GF1_MIN_OFFSET );
      snd_gf1_write8( gus, SND_GF1_VB_VOLUME_END, vol >> 4 ); /* ramp end */
      snd_gf1_write8( gus, SND_GF1_VB_VOLUME_CONTROL, ramp_ctrl );
      if ( !gus -> gf1.enh_mode ) {
        snd_gf1_delay( gus );
        snd_gf1_write8( gus, SND_GF1_VB_VOLUME_CONTROL, ramp_ctrl );
      }
      snd_spin_unlock( gus, reg, &flags );
    }
  snd_spin_lock( gus, reg, &flags );
  for ( voice = SND_GF1_PCM_VOICE_MIN(); voice <= SND_GF1_PCM_VOICE_MAX(); voice++ ) {
    snd_gf1_select_voice( gus, voice );
    if ( gus -> gf1.enh_mode )
      snd_gf1_write8( gus, SND_GF1_VB_MODE, 0x00 ); /* deactivate voice */
    snd_gf1_write8( gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl );
    voice_ctrl &= ~0x20;
  }
  voice_ctrl |= 0x20;
  if ( !gus -> gf1.enh_mode ) {
    snd_gf1_delay( gus );
    for ( voice = SND_GF1_PCM_VOICE_MIN(); voice <= SND_GF1_PCM_VOICE_MAX(); voice++ )  {
      snd_gf1_select_voice( gus, voice );
      snd_gf1_write8( gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl );
      voice_ctrl &= ~0x20;	/* disable IRQ for next voice */
    }
  }
  snd_spin_unlock( gus, reg, &flags );
#if 0
  for ( voice = SND_GF1_PCM_VOICE_MIN(); voice <= SND_GF1_PCM_VOICE_MAX(); voice++ ) {
    CLI( &flags );
    snd_gf1_select_voice( gus, voice );
    snd_gf1_print_voice_registers( gus );
    STI( &flags );
  }
#endif
}

static void snd_gf1_pcm_interrupt_wave( snd_gus_card_t *gus, int voice )
{
  unsigned long flags;
  unsigned char voice_ctrl, ramp_ctrl;
  int idx;
  unsigned int end, step;
  snd_pcm_t *pcm;

  pcm = gus -> pcm;
  if ( !pcm || voice != SND_GF1_PCM_VOICE_MIN() || !(gus -> gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE) ) {
    snd_printd( "ultra_gf1_pcm: unknown wave irq?\n" );
    snd_gf1_smart_stop_voice( gus, voice );
    return;
  }
  snd_spin_lock( gus, reg, &flags );
  voice_ctrl = snd_gf1_read8( gus, SND_GF1_VB_ADDRESS_CONTROL ) & ~0x8b; 
  ramp_ctrl = snd_gf1_read8( gus, SND_GF1_VB_VOLUME_CONTROL ) & ~0x84;
#if 0
  printk( "irq: bpos = %i\n", gus -> gf1.pcm_bpos );
#endif
  gus -> gf1.pcm_bpos++;
  gus -> gf1.pcm_bpos %= gus -> gf1.pcm_blocks;
  snd_spin_unlock( gus, reg, &flags );

#if 0
  snd_spin_lock( gus, reg, &flags );
  for ( idx = SND_GF1_PCM_VOICE_MIN(); idx <= SND_GF1_PCM_VOICE_MAX(); idx++ ) {
    snd_gf1_select_voice( gus, idx );
    snd_printk( "** curr=0x%x, begin=0x%x, end=0x%x, ctrl=0x%x, ramp=0x%x\n",
    		end = snd_gf1_read_addr( gus, SND_GF1_VA_CURRENT, voice_ctrl & 4 ),
    		snd_gf1_read_addr( gus, SND_GF1_VA_START, voice_ctrl & 4 ),
    		snd_gf1_read_addr( gus, SND_GF1_VA_END, voice_ctrl & 4 ),
    		snd_gf1_read8( gus, SND_GF1_VB_ADDRESS_CONTROL ),
    		snd_gf1_read8( gus, SND_GF1_VB_VOLUME_CONTROL ) );
  }
  snd_gf1_select_voice( gus, voice );
  snd_spin_unlock( gus, reg, &flags );
#endif

  if ( gus -> gf1.pcm_bpos + 1 >= gus -> gf1.pcm_blocks ) { /* last block? */
    voice_ctrl |= 0x08;		/* enable loop */
  } else {
    ramp_ctrl |= 0x04;		/* enable rollover */
  }
  end = gus -> gf1.pcm_memory +
        (((gus -> gf1.pcm_bpos + 1) * gus -> gf1.pcm_block_size) / SND_GF1_PCM_RVOICES());
  end -= voice_ctrl & 4 ? 2 : 1;
  step = pcm -> playback.used_size / pcm -> playback.voices;
  snd_spin_lock( gus, reg, &flags );
  voice_ctrl |= 0x20;
  for ( idx = SND_GF1_PCM_VOICE_MIN(); idx <= SND_GF1_PCM_VOICE_MAX(); idx++, end += step ) {
    snd_gf1_select_voice( gus, idx );
    snd_gf1_write_addr( gus, SND_GF1_VA_END, end << 4, voice_ctrl & 4 );
    snd_gf1_write8( gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl );
    snd_gf1_write8( gus, SND_GF1_VB_VOLUME_CONTROL, ramp_ctrl );
#if 0
    printk( "++ end=0x%x ctrl=0x%x ramp=0x%x\n", end, voice_ctrl, ramp_ctrl );
#endif
    voice_ctrl &= ~0x20;
  }
  if ( !gus -> gf1.enh_mode ) {
    snd_gf1_delay( gus );
    voice_ctrl |= 0x20;
    for ( idx = SND_GF1_PCM_VOICE_MIN(); idx <= SND_GF1_PCM_VOICE_MAX(); idx++ ) {
      snd_gf1_select_voice( gus, idx );
      snd_gf1_write8( gus, SND_GF1_VB_ADDRESS_CONTROL, voice_ctrl );
      snd_gf1_write8( gus, SND_GF1_VB_VOLUME_CONTROL, ramp_ctrl );
      voice_ctrl &= ~0x20;
    }
  }
  snd_gf1_select_voice( gus, voice );		/* return back voice */
  snd_spin_unlock( gus, reg, &flags );
  

  if ( pcm -> playback.flags & SND_PCM_FLG_MMAP ) {
    unsigned int mpos, count, pstep;

    if ( (gus -> gf1.pcm_bpos & gus -> gf1.pcm_mmap_mask) == 0 )
      pcm -> playback.ack( pcm );

    mpos = ((gus -> gf1.pcm_bpos + 1) % gus -> gf1.pcm_blocks) * gus -> gf1.pcm_block_size;
#if 1
    snd_gf1_pcm_translate( pcm -> playback.voices,
                           pcm -> playback.buffer + mpos,
		  	   gus -> gf1.pcm_buffer,
		 	   0,
			   gus -> gf1.pcm_block_size,
			   PAGE_SIZE,
			   pcm -> playback.mode,
			   0 );
#else
    memset( gus -> gf1.pcm_buffer, 0, PAGE_SIZE );
#endif
    mpos /= pcm -> playback.voices;
    count = gus -> gf1.pcm_block_size / pcm -> playback.voices;
    step = pcm -> playback.used_size / pcm -> playback.voices;
    pstep = PAGE_SIZE / pcm -> playback.voices;
    for ( idx = 0; idx < SND_GF1_PCM_VOICES(); idx++ )
      snd_gf1_pcm_mmap_change( pcm, gus, mpos + (idx * step), idx * pstep, count );
  } else {
    pcm -> playback.ack( pcm );			/* ack to high level */
  }
}

static void snd_gf1_pcm_interrupt_volume( snd_gus_card_t *gus, int voice )
{
  unsigned short vol;
  unsigned char pan;
  int cvoice;

  /* stop ramp, but leave rollover bit untouched */
  snd_gf1_i_ctrl_stop( gus, SND_GF1_VB_VOLUME_CONTROL );
  /* are we active? */
  if ( !(gus -> gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE) ) return;
  /* load real volume - better precision */
  cvoice = voice - SND_GF1_PCM_VOICE_MIN();
  if ( gus -> pcm -> playback.mode & SND_PCM_MODE_MULTI ) {
    vol = snd_gf1_pcm_volume( gus, gus -> gf1.pcm_volume[ cvoice ], pan = gus -> gf1.pcm_pan[ cvoice ] );
  } else {
    if ( gus -> pcm -> playback.voices == 2 ) pan = !cvoice ? 16 : 128 - 16; else pan = 64;
    vol = snd_gf1_pcm_volume( gus, 128, pan );
  }
  snd_gf1_i_write16( gus, SND_GF1_VW_VOLUME, vol << 4 );
}

static void snd_gf1_pcm_voices_change_start( snd_gus_card_t *gus )
{
  unsigned long flags;
  unsigned char voice_ctrl;
  
  snd_spin_lock( gus, reg, &flags );
  if ( gus -> gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE ) {
    snd_gf1_select_voice( gus, SND_GF1_PCM_VOICE_MIN() );
    voice_ctrl = snd_gf1_i_read8( gus, SND_GF1_VB_ADDRESS_CONTROL );
    gus -> gf1.pcm_pos = ( snd_gf1_i_read_addr( gus, SND_GF1_VA_CURRENT, voice_ctrl & 4 ) >> 4 ) - gus -> gf1.pcm_memory;
  }
  snd_spin_unlock( gus, reg, &flags );
  snd_gf1_stop_voices( gus, SND_GF1_PCM_VOICE_MIN(), SND_GF1_PCM_VOICE_MAX() );
}

static void snd_gf1_pcm_voices_change_stop( snd_gus_card_t *gus )
{
  snd_gf1_clear_voices( gus, SND_GF1_PCM_VOICE_MIN(), SND_GF1_PCM_VOICE_MAX() );
  if ( (gus -> gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE) && gus -> gf1.pcm_pos != SND_POS_NONE ) {
    snd_gf1_pcm_trigger_up( gus -> pcm, gus );
    gus -> gf1.pcm_pos = SND_POS_NONE;
  }
}

static void snd_gf1_pcm_volume_change( snd_gus_card_t *gus )
{
}

static int snd_gf1_pcm_playback_open( snd_pcm_t *pcm )
{
  int err, pg;
  snd_gus_card_t *gus;
  snd_gf1_mem_block_t *block;
  struct SND_STRU_GF1_VOICE_RANGE *range;

  gus = (snd_gus_card_t *)pcm -> private_data;
  if ( (err = snd_pcm_dma_alloc( pcm, SND_PCM_PLAYBACK, gus -> gf1.dma1num, "GF1 PCM - playback" )) < 0 )
    return err;
  if ( (block = snd_gf1_mem_alloc( &gus -> gf1.mem_alloc, "GF1 PCM", pcm -> playback.size, 1, 32 )) == NULL ) {
    snd_pcm_dma_free( pcm, SND_PCM_PLAYBACK, gus -> gf1.dma1num );
    return -ENOMEM;
  }
  gus -> gf1.pcm_buffer = snd_malloc_pages( PAGE_SIZE, &pg, 1 );
#if 0
  printk( "playback.buffer = 0x%lx, gf1.pcm_buffer = 0x%lx\n", (long)pcm -> playback.buffer, (long)gus -> gf1.pcm_buffer );
#endif
  if ( !gus -> gf1.pcm_buffer ) {
    snd_gf1_mem_xfree( &gus -> gf1.mem_alloc, block );
    snd_pcm_dma_free( pcm, SND_PCM_PLAYBACK, gus -> gf1.dma1num );
    return -ENOMEM;
  }
  if ( snd_gf1_dma_init( gus ) < 0 ) {
    snd_free_pages( gus -> gf1.pcm_buffer, PAGE_SIZE );
    snd_gf1_mem_xfree( &gus -> gf1.mem_alloc, block );
    snd_pcm_dma_free( pcm, SND_PCM_PLAYBACK, gus -> gf1.dma1num );
    return -ENOMEM;
  }
  snd_gf1_open( gus, SND_GF1_MODE_PCM_PLAY );
  gus -> gf1.pcm_pflags = SND_GF1_PCM_PFLG_NONE;
  range = &gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ];
  range -> interrupt_handler_wave = snd_gf1_pcm_interrupt_wave;
  range -> interrupt_handler_volume = snd_gf1_pcm_interrupt_volume;
  range -> voices_change_start = snd_gf1_pcm_voices_change_start;
  range -> voices_change_stop = snd_gf1_pcm_voices_change_stop;
  range -> volume_change = snd_gf1_pcm_volume_change;
  gus -> gf1.pcm_memory = block -> ptr;
  gus -> gf1.pcm_pos = SND_POS_NONE;	/* nothing */
  return 0;
}

static void snd_gf1_pcm_playback_close( snd_pcm_t *pcm )
{
  snd_gus_card_t *gus;
  
  gus = (snd_gus_card_t *)pcm -> private_data;
  snd_gf1_dma_done( gus );
  snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_RANGE | SND_GF1_VOICE_RANGE_PCM );
  snd_gf1_clear_voices( gus, SND_GF1_PCM_VOICE_MIN(), SND_GF1_PCM_VOICE_MAX() );
  gus -> gf1.pcm_pflags = SND_GF1_PCM_PFLG_NONE;
  if ( SND_GF1_PCM_RVOICES() > 0 ) {
    SND_GF1_PCM_RVOICES() = 0;
    snd_gf1_reselect_active_voices( gus );
  }
  snd_free_pages( gus -> gf1.pcm_buffer, PAGE_SIZE );
  gus -> gf1.pcm_buffer = NULL;
  snd_gf1_close( gus, SND_GF1_MODE_PCM_PLAY );
  snd_gf1_mem_free( &gus -> gf1.mem_alloc, gus -> gf1.pcm_memory );
  snd_pcm_dma_free( pcm, SND_PCM_PLAYBACK, gus -> gf1.dma1num ); 
}

static void snd_gf1_pcm_playback_compute_rate( snd_pcm_t *pcm )
{
  pcm -> playback.real_rate = pcm -> playback.rate;
  if ( pcm -> playback.real_rate < 5510 ) pcm -> playback.real_rate = 5510;
  if ( pcm -> playback.real_rate > 44100 ) pcm -> playback.real_rate = 44100;
}

static void snd_gf1_pcm_playback_prepare( snd_pcm_t *pcm,
                                          unsigned char *buffer,
                                          unsigned int size,
                                          unsigned int offset,
                                          unsigned int count )
{
  snd_gus_card_t *gus;
  
  gus = (snd_gus_card_t *)pcm -> private_data;
  if ( SND_GF1_PCM_RVOICES() != pcm -> playback.voices ) {
    SND_GF1_PCM_RVOICES() = pcm -> playback.voices;
    snd_gf1_reselect_active_voices( gus );
  }
  gus -> gf1.pcm_bpos = 0;
  gus -> gf1.pcm_mmap_mask = 0;
  if ( !(pcm -> playback.flags & SND_PCM_FLG_MMAP) ) {
    gus -> gf1.pcm_blocks = pcm -> playback.blocks;
    gus -> gf1.pcm_block_size = pcm -> playback.block_size;
  } else {
    int i, j, k;

    if ( pcm -> playback.real_rate <= 11025 ) j = 128; else
    if ( pcm -> playback.real_rate <= 22050 ) j = 256; else j = 512;
    if ( pcm -> playback.mode & SND_PCM_MODE_16 ) j <<= 1;
    j *= pcm -> playback.voices;
    i = pcm -> playback.block_size;
    k = 1;
    while ( j <= i ) { k <<= 1; i >>= 1; }
    k >>= 1; i <<= 1;
#ifdef SNDCFG_DEBUG
    if ( i > PAGE_SIZE ) {
      snd_printd( "ultra: gf1 pcm - MMAP block size overflow\n" );
    }
#endif
    gus -> gf1.pcm_blocks = (pcm -> playback.used_size / i);
    gus -> gf1.pcm_block_size = i;
    gus -> gf1.pcm_mmap_mask = (gus -> gf1.pcm_blocks / pcm -> playback.blocks) - 1;
  }
#if 0
  printk( "pcm blocks = %i, pcm_block_size = 0x%x, mmap_mask = 0x%x, mode = 0x%x\n", gus -> gf1.pcm_blocks, gus -> gf1.pcm_block_size, gus -> gf1.pcm_mmap_mask, pcm -> playback.mode );
#endif
  gus -> gf1.pcm_bpos = 0;
}

static void snd_gf1_pcm_playback_trigger( snd_pcm_t *pcm, int up )
{
  unsigned long flags;
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)pcm -> private_data;
  if ( up ) {
    snd_gf1_pcm_trigger_up( pcm, gus );
  } else {
#if 0
    int voice;
    for ( voice = SND_GF1_PCM_VOICE_MIN(); voice <= SND_GF1_PCM_VOICE_MAX(); voice++ ) {
      CLI( &flags );
      snd_gf1_select_voice( gus, voice );
      snd_gf1_print_voice_registers( gus );
      STI( &flags );
    }
#endif
    snd_spin_lock( gus, playback, &flags );
    gus -> gf1.pcm_pflags &= ~SND_GF1_PCM_PFLG_ACTIVE;
    snd_gf1_stop_voices( gus, SND_GF1_PCM_VOICE_MIN(), SND_GF1_PCM_VOICE_MAX() );
    snd_spin_unlock( gus, playback, &flags );
  }
}

static unsigned int snd_gf1_pcm_playback_pointer( snd_pcm_t *pcm, unsigned int used_size )
{
  unsigned long flags;
  snd_gus_card_t *gus;
  unsigned int pos;
  unsigned char voice_ctrl;
  
  gus = (snd_gus_card_t *)pcm -> private_data;
  pos = 0;
  if ( pcm -> playback.flags & SND_PCM_FLG_MMAP ) {
    pos = (gus -> gf1.pcm_bpos + 1) % gus -> gf1.pcm_blocks;
    pos *= gus -> gf1.pcm_block_size;
  } else {
    snd_spin_lock( gus, reg, &flags );
    if ( gus -> gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE ) {
      snd_gf1_select_voice( gus, SND_GF1_PCM_VOICE_MIN() );
      voice_ctrl = snd_gf1_i_read8( gus, SND_GF1_VB_ADDRESS_CONTROL );
      pos = ( snd_gf1_i_read_addr( gus, SND_GF1_VA_CURRENT, voice_ctrl & 4 ) >> 4 ) - gus -> gf1.pcm_memory;
    }
    snd_spin_lock( gus, reg, &flags );
  }
#if 0
  return gus -> gf1.pcm_bpos * gus -> gf1.pcm_block_size;
#else
  return pos;
#endif
}

static void snd_gf1_pcm_playback_dma( snd_pcm_t *pcm,
                                      unsigned char *buffer, unsigned int offset,
                                      unsigned char *user, unsigned int count )
{  
  snd_gus_card_t *gus;
  unsigned int off, step, voice;

  gus = (snd_gus_card_t *)pcm -> private_data;
  snd_gf1_pcm_translate( pcm -> playback.voices,
                         user,
			 buffer,
			 offset,
			 count,
			 pcm -> playback.used_size,
			 pcm -> playback.mode,
			 1 );
  offset /= pcm -> playback.voices;
  count /= pcm -> playback.voices;
  step = pcm -> playback.used_size / pcm -> playback.voices;
  for ( voice = 0; voice < pcm -> playback.voices; voice++ ) {
    off = offset + ( step * voice );
    snd_gf1_pcm_block_change( pcm, gus, off, count );
  }
}

static void snd_gf1_pcm_playback_dma_move( snd_pcm_t *pcm, unsigned char *buffer,
                                           unsigned int dest_offset, unsigned int src_offset,
                                           unsigned int count )
{
  unsigned int src, dest, step, voice;
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)pcm -> private_data;
  dest_offset /= pcm -> playback.voices;
  src_offset /= pcm -> playback.voices;
  count /= pcm -> playback.voices;
  step = pcm -> playback.used_size / pcm -> playback.voices;
  for ( voice = 0; voice < pcm -> playback.voices; voice++ ) {
    src = src_offset + ( step * voice );
    dest = dest_offset + ( step * voice );
    memcpy( &buffer[ dest ], &buffer[ src ], count );
    snd_gf1_pcm_block_change( pcm, gus, dest, count );
  }
}

static void snd_gf1_pcm_playback_dma_neutral( snd_pcm_t *pcm,
                                              unsigned char *buffer, unsigned int offset,
                                              unsigned int count,
                                              unsigned char neutral_byte )
{
  unsigned long flags;
  unsigned int off, step, voice;
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)pcm -> private_data;
  offset /= pcm -> playback.voices;
  count /= pcm -> playback.voices;
  step = pcm -> playback.used_size / pcm -> playback.voices;
  for ( voice = 0; voice < pcm -> playback.voices; voice++ ) {
    off = offset + ( step * voice );
    memset( &buffer[ off ], neutral_byte, count );
    snd_gf1_pcm_block_change( pcm, gus, off, count );
  }
  if ( pcm -> playback.flags & SND_PCM_FLG_NEUTRAL ) {
#if 0
    printk( "waiting!!! - jiffies = %li\n", jiffies );
#endif
    /* ok.. wait a little bit while onboard PCM buffer isn't clear */
    while ( gus -> gf1.dma_used > 0 ) {
      snd_spin_lock( gus, neutral, &flags );
      gus -> gf1.dma_flags |= SND_GF1_DMA_SLEEP;
      snd_sleep( gus, neutral, 2 * HZ );
      gus -> gf1.dma_flags &= ~SND_GF1_DMA_SLEEP;
      snd_spin_unlock( gus, neutral, &flags );
    }
#if 0
    printk( "waiting (end)!!! - jiffies = %li\n", jiffies );
#endif
  }
}

static void snd_gf1_pcm_interrupt_dma_read( snd_gus_card_t *gus )
{
  snd_gf1_i_write8( gus, SND_GF1_GB_REC_DMA_CONTROL, 0 );  /* disable sampling */
  snd_gf1_i_look8( gus, SND_GF1_GB_REC_DMA_CONTROL );  /* Sampling Control Register */
  gus -> pcm -> record.ack( gus -> pcm );
}

static int snd_gf1_pcm_record_open( snd_pcm_t *pcm )
{
  int err;
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)pcm -> private_data;
  if ( (err = snd_pcm_dma_alloc( pcm, SND_PCM_RECORD, gus -> gf1.dma2num, "GF1 PCM - record" )) < 0 )
    return err;
  snd_gf1_open( gus, SND_GF1_MODE_PCM_RECORD );
  gus -> gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read;
  return 0;  
}

static void snd_gf1_pcm_record_close( snd_pcm_t *pcm )
{
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)pcm -> private_data;
  snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_DMA_READ );
  snd_gf1_close( gus, SND_GF1_MODE_PCM_RECORD );
  snd_pcm_dma_free( pcm, SND_PCM_RECORD, gus -> gf1.dma2num ); 
}

static void snd_gf1_pcm_record_compute_rate( snd_pcm_t *pcm )
{
  pcm -> record.real_rate = pcm -> record.rate;
  if ( pcm -> record.real_rate < 5510 ) pcm -> record.real_rate = 5510;
  if ( pcm -> record.real_rate > 44100 ) pcm -> record.real_rate = 44100;  
}

static void snd_gf1_pcm_record_prepare( snd_pcm_t *pcm,
                                        unsigned char *buffer,
                                        unsigned int size,
                                        unsigned int offset,
                                        unsigned int count )
{
  snd_gus_card_t *gus;
  unsigned short tmp;

#if 0
  printk( "record prepare - buffer = 0x%lx, size = 0x%x, offset = 0x%x, count = 0x%x\n", buffer, size, offset, count );
#endif
  gus = (snd_gus_card_t *)pcm -> private_data;
#if 0
  if ( gus -> gf1.dma2 > 3 ) count >>= 1;
#endif
  tmp = ((9878400L+(pcm -> record.real_rate<<3))/(pcm -> record.real_rate<<4))-2;
  snd_gf1_i_write8( gus, SND_GF1_GB_RECORD_RATE, tmp );
  gus -> gf1.pcm_rcntrl_reg = 0x21;	/* IRQ at end, enable & start */
  if ( pcm -> record.voices > 1 )
    gus -> gf1.pcm_rcntrl_reg |= 2;
  if ( gus -> gf1.dma2 > 3 )
    gus -> gf1.pcm_rcntrl_reg |= 4;
  if ( pcm -> record.mode & SND_PCM_MODE_U )
    gus -> gf1.pcm_rcntrl_reg |= 0x80;
  snd_gf1_i_write8( gus, SND_GF1_GB_REC_DMA_CONTROL, 0 );  /* disable sampling */
  snd_gf1_i_look8( gus, SND_GF1_GB_REC_DMA_CONTROL );  /* Sampling Control Register */
  snd_dma_program( gus -> gf1.dma2, &buffer[ offset ], count, DMA_MODE_READ );
}

static void snd_gf1_pcm_record_trigger( snd_pcm_t *pcm, int up )
{
  unsigned long flags;
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)pcm -> private_data;
  snd_spin_lock( gus, reg, &flags );
  snd_gf1_write8( gus, SND_GF1_GB_REC_DMA_CONTROL, up ? gus -> gf1.pcm_rcntrl_reg : 0 );  /* go!!!! */
  snd_gf1_look8( gus, SND_GF1_GB_REC_DMA_CONTROL );
  snd_spin_unlock( gus, reg, &flags );
}

static unsigned int snd_gf1_pcm_record_pointer( snd_pcm_t *pcm, unsigned int used_size )
{
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)pcm -> private_data;
  return used_size - snd_dma_residue( gus -> gf1.dma2 );
}

static void snd_gf1_pcm_free( void *private_data );

static struct snd_stru_pcm_hardware snd_gf1_pcm_playback = {
  NULL,                         /* private data */
  NULL,                         /* private_free */
  SND_PCM_HW_AUTODMA,           /* flags */
  SND_PCM_FMT_S8 | SND_PCM_FMT_U8 |
  SND_PCM_FMT_S16_LE | SND_PCM_FMT_U16_LE,     /* formats */
  31,                           /* align value */
  6,                            /* minimal fragment */
  5510,                         /* min. rate */
  44100,                        /* max. rate */
  32,                           /* max. voices */
  snd_gf1_pcm_playback_open,
  snd_gf1_pcm_playback_close,
  snd_gf1_pcm_playback_compute_rate,
  snd_gf1_pcm_playback_prepare,
  snd_gf1_pcm_playback_trigger,
  snd_gf1_pcm_playback_pointer,
  snd_gf1_pcm_playback_dma,
  snd_gf1_pcm_playback_dma_move,
  snd_gf1_pcm_playback_dma_neutral
};

static struct snd_stru_pcm_hardware snd_gf1_pcm_record = {
  NULL,                         /* private data */
  NULL,		                /* private free */
  0,				/* flags */
  SND_PCM_FMT_S8 | SND_PCM_FMT_U8,     /* formats */
  0,                            /* align value */
  6,                            /* minimal fragment */
  5510,                         /* min. rate */
  48000,                        /* max. rate */
  2,                            /* max. voices */
  snd_gf1_pcm_record_open,
  snd_gf1_pcm_record_close,
  snd_gf1_pcm_record_compute_rate,
  snd_gf1_pcm_record_prepare,
  snd_gf1_pcm_record_trigger,
  snd_gf1_pcm_record_pointer,
  snd_pcm_record_dma,
  snd_pcm_dma_move,
  NULL
};

static void snd_gf1_pcm_free( void *private_data )
{
  ((snd_gus_card_t *)private_data) -> pcm = NULL;
}

static void snd_gf1_pcm_volume_level( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int left, int right )
{
  snd_gus_card_t *gus;
  unsigned long flags;
  unsigned short vol;
  unsigned char pan;
  int voice;

  gus = (snd_gus_card_t *)channel -> private_data;
  snd_spin_lock( gus, pcm_volume_level, &flags );
  gus -> gf1.pcm_volume_level_left = SND_GF1_VOLUME( left );
  gus -> gf1.pcm_volume_level_right = SND_GF1_VOLUME( right );
  snd_spin_unlock( gus, pcm_volume_level, &flags );
  /* are we active? */
  if ( !(gus -> gf1.pcm_pflags & SND_GF1_PCM_PFLG_ACTIVE) ) return;
  /* load real volume - better precision */
  for ( voice = 0; voice < SND_GF1_PCM_VOICES(); voice++ ) {
    snd_spin_lock( gus, reg, &flags );
    snd_gf1_select_voice( gus, voice + SND_GF1_PCM_VOICE_MIN() );
    snd_gf1_ctrl_stop( gus, SND_GF1_VB_VOLUME_CONTROL );
    if ( gus -> pcm -> playback.mode & SND_PCM_MODE_MULTI ) {
      vol = snd_gf1_pcm_volume( gus, gus -> gf1.pcm_volume[ voice ], pan = gus -> gf1.pcm_pan[ voice ] );
    } else {
      if ( gus -> pcm -> playback.voices == 2 ) pan = !voice ? 8 : 128 - 9; else pan = 64;
      vol = snd_gf1_pcm_volume( gus, 128, pan );
    }
    snd_gf1_write16( gus, SND_GF1_VW_VOLUME, vol << 4 );
    snd_spin_unlock( gus, reg, &flags );
  }
}

snd_pcm_t *snd_gf1_pcm_new_device( snd_gus_card_t *gus, snd_kmixer_t *mixer )
{
  static struct snd_stru_mixer_channel_hw pcm_mix = {
    SND_MIXER_PRI_PCM,          /* priority */
    SND_MIXER_PRI_PARENT,       /* parent priority */
    SND_MIXER_ID_PCM,           /* device name */
    SND_MIXER_OSS_PCM,		/* OSS device # */
    0, 1, 0, 1, 0,              /* mute/stereo/record/digital/input */
    0, 127,                     /* min, max value */
    -9450, 0, 150,              /* min, max, step - dB */
    0,			        /* private value */
    NULL,                       /* compute dB -> linear */
    NULL,                       /* compute linear -> dB */
    NULL,                       /* record source */
    NULL,			/* set mute */
    snd_gf1_pcm_volume_level,   /* set volume level */
  };
  static struct snd_stru_mixer_channel_hw pcm1_mix = {
    SND_MIXER_PRI_PCM1,         /* priority */
    SND_MIXER_PRI_PARENT,       /* parent priority */
    SND_MIXER_ID_PCM1,          /* device name */
    SND_MIXER_OSS_SPEAKER,	/* OSS device # */
    0, 1, 0, 1, 0,              /* mute/stereo/record/digital/input */
    0, 127,                     /* min, max value */
    -9450, 0, 150,              /* min, max, step - dB */
    0,			        /* private value */
    NULL,                       /* compute dB -> linear */
    NULL,                       /* compute linear -> dB */
    NULL,                       /* record source */
    NULL,			/* set mute */
    snd_gf1_pcm_volume_level,   /* set volume level */
  };

  snd_card_t *card;
  snd_pcm_t *pcm;
  snd_kmixer_channel_t *channel;
  struct snd_stru_mixer_channel_hw *pmix;

  card = gus -> card;
  pcm = snd_pcm_new_device( card, gus -> interwave ? "AMD InterWave" : "GF1" );
  if ( !pcm ) return NULL;
  pcm -> private_data = gus;
  pcm -> private_free = snd_gf1_pcm_free;
  memcpy( &pcm -> playback.hw, &snd_gf1_pcm_playback, sizeof( snd_gf1_pcm_playback ) );
  pcm -> info_flags = SND_PCM_INFO_MMAP | SND_PCM_INFO_PLAYBACK;
  if ( !gus -> interwave && !gus -> ess_flag ) {
    memcpy( &pcm -> record.hw, &snd_gf1_pcm_record, sizeof( snd_gf1_pcm_record ) );
    pcm -> info_flags |= SND_PCM_INFO_RECORD;
    if ( gus -> gf1.dma1num != gus -> gf1.dma2num ) 
      pcm -> info_flags |= SND_PCM_INFO_DUPLEX;
  }
  strcpy( pcm -> name, pcm -> id );
  if ( gus -> interwave ) {
    sprintf( pcm -> name + strlen( pcm -> name ), " rev %c", gus -> revision + 'A' );
  }
  strcat( pcm -> name, " (synth)" );
  gus -> pcm = pcm;
  channel = snd_mixer_find_channel( mixer, SND_MIXER_PRI_PCM );
  pmix = channel ? &pcm1_mix : &pcm_mix;
  channel = snd_mixer_new_channel( mixer, pmix );
#ifdef SNDCFG_DEBUG
  if ( !channel ) {
    snd_printk( "Oops.... GF1 PCM mixer channel can't be registered!!!" );
    return pcm;
  }
#endif
  channel -> private_data = gus;
  return pcm;
}
