/*
 *  Digital Audio (PCM) abstract layer / OSS compatible
 *  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_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "minors.h"
#include "pcm.h"
#include "ulaw.h"

#ifdef LINUX_2_1
EXPORT_NO_SYMBOLS;
#endif

extern snd_pcm_t *snd_pcm_devices[ SND_CARDS * SND_PCM_DEVICES ];
extern int (*snd_pcm_oss_register)( unsigned short minor );
extern int (*snd_pcm_oss_unregister)( unsigned short minor );

#if 0
#define SND_PCM_DEBUG_BUFFERS
#endif

#if 0	/* not used now - should be used for debugging */
#define SND_PCM_SKIP_LOCKED_BLOCKS
#endif

int snd_mixer_ioctl_card( snd_card_t *card, struct file *file, unsigned int cmd, unsigned long arg );

static void snd_pcm_oss_compute_blocks( snd_pcm_t *pcm, int direction );
static int snd_pcm_oss_nonblock( snd_pcm_t *pcm, int flags );

/*
 *  interrupt callbacks from lowlevel driver
 */
 
static void snd_pcm_oss_interrupt_playback( snd_pcm_t *pcm )
{
  unsigned long flags;
  snd_pcm_channel_t *pchn;
  int trigger = 0;
  
  pchn = &pcm -> playback;
  if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER) ) {
    snd_printd( "snd_pcm_oss_interrupt_playback: Oops, playback interrupt when playback isn't active!!!\n" );
    return;
  }
  if ( !(pchn -> flags & SND_PCM_FLG_MMAP) ) {
    /* try clear first few bytes from next block */
    if ( pchn -> hw.flags & SND_PCM_HW_AUTODMA ) {
      pchn -> hw.dma_neutral( pcm, pchn -> buffer, pchn -> tail * pchn -> block_size, 16, pchn -> neutral_byte );
    }
    if ( pchn -> used == 1 ) {
      if ( !(pchn -> flags & SND_PCM_FLG_SYNC) ) {
#ifdef SND_PCM_DEBUG_BUFFERS
        printk( "playback: underrun!!! - jiffies = %li\n", jiffies );
#endif
        pchn -> total_xruns++;
        pchn -> xruns++;
      }
    }
    snd_spin_lock( pcm, playback, &flags );
    pchn -> interrupts++;
    if ( pchn -> used > 0 )
      pchn -> processed_bytes += pchn -> block_size;
    pchn -> tail++;
    pchn -> tail %= pchn -> blocks;
    pchn -> used--;
    if ( pchn -> used > 0 ) {
      snd_pcm_lock( pchn, pchn -> tail );
      snd_spin_unlock( pcm, playback, &flags );
    } else {
      snd_pcm_lockzero( pchn );
      pchn -> head = pchn -> tail = 0;
      snd_spin_unlock( pcm, playback, &flags );
      if ( pchn -> frag_size > 0 )
        pchn -> hw.dma_move( pcm, pchn -> buffer, 0, pchn -> head * pchn -> block_size, pchn -> frag_size );
    }
#if 0
    printk( "interrupt!!! tail = %i, used = %i\n", pchn -> tail, pchn -> used );
#endif
    if ( pchn -> used > 0 ) trigger = 1;
  } else {
    snd_spin_lock( pcm, playback, &flags );
    pchn -> interrupts++;
    pchn -> processed_bytes += pchn -> block_size;
    pchn -> tail++;
    pchn -> tail %= pchn -> blocks;
    snd_spin_unlock( pcm, playback, &flags );
    trigger = 1;
  }
  if ( trigger && !(pchn -> hw.flags & SND_PCM_HW_AUTODMA) ) {
    pchn -> hw.prepare( pcm,
            		pchn -> buffer, pchn -> used_size,            			
            		pchn -> tail * pchn -> block_size,
                        pchn -> block_size );
    pchn -> hw.trigger( pcm, 1 );
  }
  if ( !trigger ) {
    if ( pchn -> hw.flags & SND_PCM_HW_AUTODMA ) {
      pchn -> hw.trigger( pcm, 0 );
    }
    pchn -> flags &= ~SND_PCM_FLG_TRIGGER;
  }
  if ( pchn -> flags & SND_PCM_FLG_SLEEP )
    snd_wakeup( pcm, playback );
}

static void snd_pcm_oss_interrupt_record( snd_pcm_t *pcm )
{
  unsigned long flags;
  snd_pcm_channel_t *pchn;
  int trigger;
  
  pchn = &pcm -> record;
  if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER) ) {
    snd_printd( "snd_pcm_oss_interrupt_record: Oops, record interrupt when record isn't active!!!\n" );
    return;
  }
  trigger = 0;
  snd_spin_lock( pcm, record, &flags );
  if ( !(pchn -> flags & SND_PCM_FLG_MMAP) ) {
    if ( pchn -> used < pchn -> blocks ) {
      pchn -> used++;
    } else {
      pchn -> total_xruns++;
      pchn -> xruns++;
    }
    pchn -> interrupts++;
    pchn -> processed_bytes += pchn -> block_size;
    pchn -> head++;
    pchn -> head %= pchn -> blocks;
    snd_pcm_lock( pchn, pchn -> head );
  } else {
    pchn -> interrupts++;
    pchn -> processed_bytes += pchn -> block_size;
    pchn -> head++;
    pchn -> head %= pchn -> blocks;
  }
  snd_spin_unlock( pcm, record, &flags );
  if ( !(pchn -> flags & SND_PCM_FLG_SYNC) ) trigger = 1;
  if ( trigger && !(pchn -> hw.flags & SND_PCM_HW_AUTODMA) ) {
    pchn -> hw.prepare( pcm,
            		pchn -> buffer, pchn -> used_size,            			
            		pchn -> head * pchn -> block_size,
                        pchn -> block_size );
    pchn -> hw.trigger( pcm, 1 );
  }
  if ( !trigger ) { 
    if ( pchn -> hw.flags & SND_PCM_HW_AUTODMA ) {
      pchn -> hw.trigger( pcm, 0 );
    }
    pchn -> flags &= ~SND_PCM_FLG_TRIGGER;
  }
  if ( pchn -> flags & SND_PCM_FLG_SLEEP )
    snd_wakeup( pcm, record );
}

/*
 *  trigger standard buffered playback
 */

static void snd_pcm_oss_trigger_playback( snd_pcm_t *pcm, snd_pcm_channel_t *pchn )
{
  unsigned long flags;

  if ( !pchn -> used ) return;
  snd_spin_lock( pcm, playback, &flags );
  if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER) ) {
    snd_spin_unlock( pcm, playback, &flags );
    pchn -> flags |= SND_PCM_FLG_TRIGGERA;
    pchn -> hw.prepare( pcm,
            		pchn -> buffer, pchn -> used_size,            			
            		pchn -> tail * pchn -> block_size,
                        pchn -> block_size );
    pchn -> hw.trigger( pcm, 1 );
    snd_spin_lock( pcm, playback, &flags );
    snd_pcm_lock( pchn, pchn -> tail );
#if 0
    if ( !pchn -> processed_bytes ) {
      pchn -> interrupts++;
      pchn -> processed_bytes += pchn -> block_size;
    }
#endif
  }
  snd_spin_unlock( pcm, playback, &flags );
}

/*
 *  user to dma
 */

static int snd_pcm_oss_user_to_buffer( snd_pcm_t *pcm, const char *buf, int count )
{
  unsigned long flags;
  snd_pcm_channel_t *pchn;
  int result, tmp;

  if ( count <= 0 || !buf ) return 0;
  if ( verify_area( VERIFY_READ, buf, count ) ) return -EFAULT;

  pchn = &pcm -> playback;
#if 0
  snd_printk( "snd_pcm_oss_user_to_buffer: buf=0x%x, count=0x%x,%i (%s), used = %i, jiffies = %li\n", (int)buf, count, count, pchn -> flags & SND_PCM_FLG_NONBLK ? "non blocked" : "blocked", pchn -> used, jiffies );
#endif
  count &= ~( (pchn -> voices * (pchn -> mode & SND_PCM_MODE_16 ? 2 : 1)) - 1 );
  pchn -> flags &= ~SND_PCM_FLG_ABORT;
  if ( pchn -> flags & SND_PCM_FLG_MMAP ) return -EIO;	/* go to hell... */
  if ( pchn -> flags & SND_PCM_FLG_BUFFERS )
    snd_pcm_oss_compute_blocks( pcm, SND_PCM_PLAYBACK );
  if ( pchn -> flags & SND_PCM_FLG_NEUTRAL )
    snd_pcm_fill_with_neutral( pcm, pchn );

  result = 0;

  /*
   * OK. This is patch for .au files which begin with .snd header...
   * This is a little bit hard - it's application work to do conversions...
   */
  if ( (pchn -> mode & SND_PCM_MODE_ULAW) && !pchn -> processed_bytes && !pchn -> used && count > 31 )
    {
      unsigned char buffer[ 8 ];
      
      copy_from_user( buffer, buf, 8 );
      if ( !memcmp( buffer, ".snd", 4 ) )
        {
          tmp = (buffer[4]<<24)|(buffer[5]<<16)|(buffer[6]<<8)|(buffer[7]);
          if ( tmp > count ) tmp = count;
          if ( tmp < 128 ) {
            buf += tmp;
            count -= tmp;
            result += tmp;
          }
        }
    }

  while ( count > 0 )
    {
      while ( pchn -> used >= pchn -> blocks )
        {
          if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER) )
            snd_pcm_oss_trigger_playback( pcm, pchn );
          if ( pchn -> flags & SND_PCM_FLG_NONBLK ) return result;
          if ( !(pchn -> flags & SND_PCM_FLG_ENABLE) ) return result;
          snd_spin_lock( pcm, playback_sleep, &flags );
          pchn -> flags |= SND_PCM_FLG_SLEEP;
          snd_sleep( pcm, playback, 10 * HZ );
          pchn -> flags &= ~SND_PCM_FLG_SLEEP;
          snd_spin_unlock( pcm, playback_sleep, &flags );
          if ( snd_sleep_abort( pcm, playback ) )
            {
#ifndef LINUX_2_1
#ifdef SND_PCM_DEBUG_BUFFERS
	      printk( "playback: abort!!! signal = 0x%lx, jiffies = %li\n", current -> signal & ~current -> blocked, jiffies );
#endif
#if 0
	      if ( (current -> signal & ~current -> blocked) == 0x2000 ) continue;
#endif
#endif
              pchn -> flags |= SND_PCM_FLG_ABORT;
              return result;
            }
          if ( pchn -> used >= pchn -> blocks && snd_timeout( pcm, playback ) )
            {
              snd_printd( "pcm_user_to_dma: timeout, new block discarded\n" );
              return -EIO;
            }
        }
        
#ifdef SND_PCM_SKIP_LOCKED_BLOCKS
      /* ok.. interresant part.. we must find first free & not locked block.. */        
      tmp = 0;
      snd_spin_lock( pcm, playback, &flags );
      while ( snd_pcm_islock( pchn, pchn -> head ) )
        {
          pchn -> hw.discarded++;
#ifdef SND_PCM_DEBUG_BUFFERS
          printk( "playback: discarded!!! %i - %i\n", pchn -> block_lock, pchn -> head );
#endif
          pchn -> used++;
          pchn -> head++;
          pchn -> head %= pchn -> blocks;
          if ( pchn -> used >= pchn -> blocks )
            {
              tmp++;
              break;
            }
        }
      snd_spin_unlock( pcm, playback, &flags );
      if ( tmp ) continue;		/* go to sleep loop */
#endif

      /* ok.. now we have right block - fill it */
      
      tmp = pchn -> block_size - pchn -> frag_size;
      if ( tmp > count ) tmp = count;	/* correction */

#if 0
      printk( "to dma: size = %i\n", tmp );
#endif
      if ( verify_area( VERIFY_READ, buf, tmp ) ) return -EFAULT;
      pchn -> hw.dma( pcm,
                      pchn -> buffer,
                      ( pchn -> head * pchn -> block_size ) + pchn -> frag_size,
                      (char *)buf, tmp );
      snd_pcm_proc_write( pchn, buf, tmp );

      count -= tmp;
      result += tmp;
      buf += tmp;

      snd_spin_lock( pcm, playback, &flags );
      pchn -> frag_size += tmp;
      if ( pchn -> frag_size >= pchn -> block_size )
        {
#if 0
          printk( "block filled = %i, used = %i, blocks = %i\n", pchn -> head, pchn -> used, pchn -> blocks );
#endif
          pchn -> used++;
          pchn -> head++;
          pchn -> head %= pchn -> blocks;
#if 0
          pchn -> processed_bytes += pchn -> frag_size;
#endif
          pchn -> frag_size = 0;
        }
      snd_spin_unlock( pcm, playback, &flags );
    }
#if 0
  snd_printk( "pcm_user_to_dma: end\n" );
#endif
  if ( pchn -> used )
    snd_pcm_oss_trigger_playback( pcm, pchn );
  return result;
}

/*
 *  trigger standard buffered record
 */

static void snd_pcm_oss_trigger_record( snd_pcm_t *pcm, snd_pcm_channel_t *pchn )
{
  unsigned long flags;

  if ( pchn -> used >= pchn -> blocks ) return;
  snd_spin_lock( pcm, record, &flags );
  if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER) ) {
    snd_spin_unlock( pcm, record, &flags );
    pchn -> flags |= SND_PCM_FLG_TRIGGERA;
    pchn -> hw.prepare( pcm,
            		pchn -> buffer, pchn -> used_size,            			
            		pchn -> head * pchn -> block_size,
                        pchn -> block_size );
    pchn -> hw.trigger( pcm, 1 );
    snd_spin_lock( pcm, record, &flags );
    snd_pcm_lock( pchn, pchn -> head );
#if 0
    if ( !pchn -> processed_bytes ) {
      pchn -> interrupts++;
      pchn -> processed_bytes += pchn -> block_size;
    }
#endif
  }
  snd_spin_unlock( pcm, record, &flags );
}

/*
 *  dma to user
 */

static int snd_pcm_oss_buffer_to_user( snd_pcm_t *pcm, char *buf, int count )
{
  unsigned long flags;
  snd_pcm_channel_t *pchn;
  int result, tmp;

  if ( count <= 0 ) return 0;
  if ( verify_area( VERIFY_WRITE, buf, count ) ) return -EFAULT;
  
  pchn = &pcm -> record;
#if 0
  snd_printk( "snd_pcm_user_oss_buffer_to_user: buf=0x%x, count=0x%x (%s)\n", (int)buf, count, pchn -> flags & SND_PCM_FLG_NONBLK ? "non blocked" : "blocked" );
#endif
  count &= ~( (pchn -> voices * (pchn -> mode & SND_PCM_MODE_16 ? 2 : 1)) - 1 );
  pchn -> flags &= ~SND_PCM_FLG_ABORT;
  if ( pchn -> flags & SND_PCM_FLG_MMAP ) return -EIO;
  if ( pchn -> flags & SND_PCM_FLG_BUFFERS )
    snd_pcm_oss_compute_blocks( pcm, SND_PCM_RECORD );
  if ( pchn -> flags & SND_PCM_FLG_NEUTRAL )
    snd_pcm_fill_with_neutral( pcm, pchn );

  if ( !(pchn -> flags & SND_PCM_FLG_ENABLE) ) return 0;
  snd_pcm_oss_trigger_record( pcm, pchn );
  result = 0;
  
  while ( count > 0 )
    {
      while ( !pchn -> used )
        {
          if ( pchn -> flags & SND_PCM_FLG_NONBLK ) return result;
          snd_spin_lock( pcm, record_sleep, &flags );
          pchn -> flags |= SND_PCM_FLG_SLEEP;
          snd_sleep( pcm, record, 10 * HZ );
          pchn -> flags &= ~SND_PCM_FLG_SLEEP;
          snd_spin_unlock( pcm, record_sleep, &flags );
          if ( snd_sleep_abort( pcm, record ) )
            {
              pchn -> flags |= SND_PCM_FLG_ABORT;
              return -EINTR;
            }
          if ( !pchn -> used && snd_timeout( pcm, record ) )
            {
              snd_printd( "snd_pcm_dma_to_user: data timeout\n" );
              return -EIO;
            }
        }

#ifdef SND_PCM_SKIP_LOCKED_BLOCKS
      tmp = 0;
      snd_spin_lock( pcm, record, &flags );
      while ( snd_pcm_islock( pchn, pchn -> tail ) )
        {
          pchn -> hw.discarded++;
          pchn -> used--;
          pchn -> tail++;
          pchn -> tail %= pchn -> blocks;
          if ( !pchn -> used )
            {
              tmp++;
              break;
            }
        }
      snd_spin_unlock( pcm, record, &flags );
      if ( tmp ) continue;		/* go to sleep loop */
#endif
        
      tmp = count <= pchn -> frag_size ? count : pchn -> frag_size;
      if ( verify_area( VERIFY_WRITE, buf, tmp ) ) return -EFAULT;
      pchn -> hw.dma( pcm,
                      pchn -> buffer,
      		      ( pchn -> tail * pchn -> block_size ) + ( pchn -> block_size - pchn -> frag_size ),
                      buf, tmp );
      if ( !verify_area( VERIFY_READ, buf, tmp ) )
        snd_pcm_proc_write( pchn, buf, tmp );

      buf += tmp;
      count -= tmp;
      result += tmp;

      snd_spin_lock( pcm, record, &flags );
      pchn -> frag_size -= tmp;
      if ( !pchn -> frag_size )
        {
          pchn -> used--;
          pchn -> tail++;
          pchn -> tail %= pchn -> blocks;
          pchn -> frag_size = pchn -> block_size;
        }
      snd_spin_unlock( pcm, record, &flags );
    }

  return result;
}

/*
 *  synchronize playback
 */

static void snd_pcm_oss_sync_playback( snd_pcm_t *pcm )
{
  unsigned long flags;
  snd_pcm_channel_t *pchn;

  pchn = &pcm -> playback;
  if ( pchn -> flags & SND_PCM_FLG_ABORT ) {
    if ( pchn -> flags & SND_PCM_FLG_TRIGGER ) {
      pchn -> hw.trigger( pcm, 0 );
      pchn -> flags &= ~SND_PCM_FLG_TRIGGER;
    }
    goto __end_done;
  }
  pchn -> flags |= SND_PCM_FLG_SYNC;
  if ( pchn -> used < pchn -> blocks && pchn -> frag_size > 0 )
    {
      unsigned int size = pchn -> frag_size;
      unsigned int count = pchn -> block_size - size;
      unsigned int offset;;
      
      if ( count > 0 ) {
        offset = ( pchn -> head * pchn -> block_size ) + size;
        pchn -> hw.dma_neutral( pcm, pchn -> buffer, offset, count, pchn -> neutral_byte );
      }
      pchn -> used++;
      pchn -> head++;
      pchn -> head %= pchn -> blocks;
    }
  if ( !pchn -> used ) goto __end;	/* probably mmaped access */

  snd_pcm_oss_trigger_playback( pcm, pchn );

  while ( pchn -> used ) {
    snd_spin_lock( pcm, playback_sleep, &flags );
    pchn -> flags |= SND_PCM_FLG_SLEEP;
    snd_sleep( pcm, playback, 10 * HZ );
    pchn -> flags &= ~SND_PCM_FLG_SLEEP;
    snd_spin_unlock( pcm, playback_sleep, &flags );
    if ( snd_sleep_abort( pcm, playback ) ) {
      pchn -> flags |= SND_PCM_FLG_ABORT;
      pchn -> hw.trigger( pcm, 0 );
      pchn -> flags &= ~SND_PCM_FLG_TRIGGER;
      goto __end_done;
    }
    if ( pchn -> used && snd_timeout( pcm, playback ) ) {
      snd_printd( "snd_pcm_oss_sync_playback: timeout, skipping waiting blocks\n" );
      return;
    }
  }
  
  __end_done:
  pchn -> flags |= SND_PCM_FLG_NEUTRAL;
  snd_pcm_fill_with_neutral( pcm, pchn );
  pchn -> flags &= ~SND_PCM_FLG_TRIGGER1;
  pchn -> head = pchn -> tail = pchn -> used = 0;
  pchn -> frag_size = 0;
  snd_pcm_lockzero( pchn );
  __end:
  pchn -> flags &= ~SND_PCM_FLG_SYNC;
}

static void snd_pcm_oss_sync_record( snd_pcm_t *pcm )
{
  unsigned long flags;
  snd_pcm_channel_t *pchn;

  pchn = &pcm -> record;
  pchn -> flags |= SND_PCM_FLG_SYNC;
  while ( pchn -> flags & SND_PCM_FLG_TRIGGER ) {	/* still recording? */
    snd_spin_lock( pcm, record_sleep, &flags );
    pchn -> flags |= SND_PCM_FLG_SLEEP;
    snd_sleep( pcm, record, 10 * HZ );
    pchn -> flags &= ~SND_PCM_FLG_SLEEP;
    snd_spin_unlock( pcm, record_sleep, &flags );
    if ( snd_sleep_abort( pcm, record ) ) {
      pchn -> flags |= SND_PCM_FLG_ABORT;
      pchn -> hw.trigger( pcm, 0 );
      pchn -> flags &= ~(SND_PCM_FLG_SYNC|SND_PCM_FLG_TRIGGER);
      return;
    }
    if ( (pchn -> flags & SND_PCM_FLG_TRIGGER) && snd_timeout( pcm, record ) )
      { snd_printd( "snd_pcm_sync_record: sync timeout\n" );
      pchn -> flags &= ~SND_PCM_FLG_SYNC;
      break;
    }    
  }
  pchn -> flags &= ~SND_PCM_FLG_SYNC;
  snd_pcm_fill_with_neutral( pcm, pchn );
  pchn -> flags &= ~SND_PCM_FLG_TRIGGER1;
  pchn -> head = pchn -> tail = pchn -> used = 0;
  pchn -> frag_size = pchn -> block_size;
  snd_pcm_lockzero( pchn );
}

static int snd_pcm_oss_sync( snd_pcm_t *pcm, unsigned short flags )
{
  if ( flags & SND_PCM_LFLG_PLAY ) snd_pcm_oss_sync_playback( pcm );
  if ( flags & SND_PCM_LFLG_RECORD ) snd_pcm_oss_sync_record( pcm );
  return 0;
}

/*
 *  other things
 */

static void snd_pcm_oss_compute_blocks( snd_pcm_t *pcm, int direction )
{
  unsigned int block, blocks, bps, min;
  unsigned long flags;
  snd_pcm_channel_t *pchn;

  if ( direction == SND_PCM_PLAYBACK ) {
    pchn = &pcm -> playback;
    snd_pcm_oss_sync_playback( pcm );
    snd_spin_lock( pcm, playback, &flags );
  } else {
    pchn = &pcm -> record;
    snd_pcm_oss_sync_record( pcm );
    snd_spin_lock( pcm, record, &flags );
  }
  pchn -> used_size = pchn -> mmap_size > 0 ? pchn -> mmap_size : pchn -> size;
  if ( pchn -> hw.flags & SND_PCM_HW_8BITONLY ) {
    if ( pchn -> mode & SND_PCM_MODE_16 ) pchn -> used_size <<= 1;
  }
  if ( pchn -> hw.flags & SND_PCM_HW_16BITONLY ) {
    if ( !(pchn -> mode & SND_PCM_MODE_16) ) pchn -> used_size >>= 1;
  }
#ifdef SND_PCM_DEBUG_BUFFERS
  printk( "pchn -> used_size = %i, size = %i, mode = 0x%x\n", pchn -> used_size, pchn -> size, pchn -> mode );
#endif
  for ( min = 1, bps = pchn -> hw.min_fragment; bps > 1; bps-- ) min <<= 1;
  if ( pchn -> requested_block_size > 0 )
    {
      block = pchn -> requested_block_size;
      block &= ~(pchn -> hw.align|3);
      if ( block < min ) block = min;
      blocks = pchn -> used_size / block;
      if ( blocks > pchn -> requested_blocks )
        blocks = pchn -> requested_blocks;
    }
   else
    {
      bps = pchn -> rate * pchn -> voices;
      if ( pchn -> mode & SND_PCM_MODE_16 ) bps <<= 1;
      if ( pchn -> mode & SND_PCM_MODE_ADPCM ) bps >>= 2;
      bps >>= 2;			/* make only fragment for 0.25 sec */
      if ( bps <= 0 ) bps = 16;
      block = pchn -> used_size;
      while ( block > bps ) block >>= 1;
      if ( block == pchn -> used_size ) block >>= 1;
#if 0
      if ( record )
        block /= 4;			/* small fragment when recording */
#endif
      block &= ~(pchn -> hw.align|3);	/* align to 4 */
      if ( block < min ) block = min;
      blocks = pchn -> used_size / block;
#if 0
      if ( blocks > 8 ) blocks = 8;	/* max 2 sec buffer */
#endif
    }
#if 0
  max = pchn -> hw_max_dma_block( card );
  if ( max < block ) block = max;
#endif
  if ( blocks < 2 ) {
    blocks = 2;
    if ( block > pchn -> used_size >> 1 )
      block = pchn -> used_size >> 1;
  }
  if ( blocks > 128 ) blocks = 128;
  pchn -> used_size = blocks * block;
  pchn -> blocks = blocks;
  pchn -> block_size = block;
  pchn -> flags |= SND_PCM_FLG_NEUTRAL;
  pchn -> flags &= ~SND_PCM_FLG_BUFFERS;
  if ( direction == SND_PCM_RECORD ) {
    pchn -> frag_size = pchn -> block_size;
    snd_spin_unlock( pcm, record, &flags );
  } else {
    snd_spin_unlock( pcm, playback, &flags );
  }
#ifdef SND_PCM_DEBUG_BUFFERS
  snd_printk( "used_size = %i, blocks = %i, blocks_size = %i, mmap = %s\n", pchn -> used_size, pchn -> blocks, pchn -> block_size, pchn -> flags & SND_PCM_FLG_MMAP ? "yes" : "no" );
#endif
}

static int snd_pcm_oss_set_subdivision( snd_pcm_t *pcm, unsigned short flags, unsigned int subdivision )
{
  int idx;
  snd_pcm_channel_t *pchn;

  for ( idx = 1; idx <= 2; idx++ )
    if ( flags & idx )
      {
        pchn = idx == 1 ? &pcm -> playback : &pcm -> record;
        if ( pchn -> requested_subdivision != subdivision )
          {
            if ( idx == 1 )
              snd_pcm_oss_sync_playback( pcm );
             else
              snd_pcm_oss_sync_record( pcm );
            pchn -> requested_subdivision = subdivision;
            pchn -> flags |= SND_PCM_FLG_NEUTRAL | SND_PCM_FLG_BUFFERS;
          }
      }  
  return 0;
}

/*
 * SET FRAGMENT notes:
 *   Real Video 5.0b3 - (1) fragment = 0x2e00004 (rate & format set before)
 *                      (2) fragment = 0x04 (rate & format set before)
 *   Libmmoss 1.1 - fragment = 0x20009 (rate & format set before)
 *   MTV - fragment = 0x7f000a (rate & format set after!!)
 */

static int snd_pcm_oss_set_fragment( snd_pcm_t *pcm, unsigned short flags, unsigned int fragment )
{
  int idx, size, size1, bytes, count, min_fragment;
  snd_pcm_channel_t *pchn;

  size = 0x10000;
  count = (fragment >> 16) & 0xffff;
  bytes = fragment & 0xffff;
  if ( !bytes ) {
    if ( flags & SND_PCM_LFLG_PLAY )
      size = pcm -> playback.requested_block_size;
    if ( flags & SND_PCM_LFLG_RECORD )
      size = pcm -> record.requested_block_size;
    if ( !size ) size = 0x10000;
    size1 = size;
    while ( size1 > 0 ) { bytes++; size1 >>= 1; }
  } else {
    min_fragment = 4;
    if ( flags & SND_PCM_LFLG_PLAY ) 
      if ( pcm -> playback.hw.min_fragment > min_fragment )
        min_fragment = pcm -> playback.hw.min_fragment;
    if ( flags & SND_PCM_LFLG_RECORD ) 
      if ( pcm -> record.hw.min_fragment > min_fragment )
        min_fragment = pcm -> record.hw.min_fragment;
    if ( bytes < min_fragment ) bytes = min_fragment;
    if ( bytes > 17 ) bytes = 17;
    size = 1 << bytes;
  }
  if ( !count ) {
    count = 128;
  } else {
    if ( count < 2 ) count = 2;
    if ( count > 128 ) count = 128;
  }

#ifdef SND_PCM_DEBUG_BUFFERS
  printk( "set fragment: fragment = 0x%x, size = %i, count = %i\n", fragment, size, count );
#endif

  for ( idx = 1; idx <= 2; idx++ )
    if ( flags & idx )
      {
        pchn = idx == 1 ? &pcm -> playback : &pcm -> record;
        if ( pchn -> requested_block_size != size ||
             pchn -> requested_blocks != count )
          {
            if ( idx == 1 )
              snd_pcm_oss_sync_playback( pcm );
             else
              snd_pcm_oss_sync_record( pcm );
            pchn -> requested_block_size = size;
            pchn -> requested_blocks = count;
            pchn -> flags |= SND_PCM_FLG_NEUTRAL | SND_PCM_FLG_BUFFERS;
          }
      }  

  return ( count << 16 ) | bytes;
}

static unsigned int snd_pcm_oss_get_block_size( snd_pcm_t *pcm, unsigned short flags )
{
  snd_card_t *card;

  card = pcm -> card;
  if ( ( flags & SND_PCM_LFLG_PLAY ) && ( pcm -> playback.flags & SND_PCM_FLG_BUFFERS ) )
    snd_pcm_oss_compute_blocks( pcm, SND_PCM_PLAYBACK );
  if ( ( flags & SND_PCM_LFLG_RECORD ) && ( pcm -> record.flags & SND_PCM_FLG_BUFFERS ) )
    snd_pcm_oss_compute_blocks( pcm, SND_PCM_RECORD );
  return (flags & 1) ? pcm -> playback.block_size : pcm -> record.block_size;
}

static void snd_pcm_oss_set_mode( snd_pcm_t *pcm, unsigned short flags, unsigned int mode )
{
  int idx;
  snd_pcm_channel_t *pchn;

#ifdef SND_PCM_DEBUG_BUFFERS
  printk( "mode = 0x%x\n", mode );
#endif
  for ( idx = 1; idx <= 2; idx++ )
    if ( flags & idx )
      {
        pchn = idx == 1 ? &pcm -> playback : &pcm -> record;
        if ( pchn -> mode != mode )
          {
            if ( idx == 1 )
              snd_pcm_oss_sync_playback( pcm );
             else 
              snd_pcm_oss_sync_record( pcm );
            pchn -> neutral_byte = ( mode & SND_PCM_MODE_16 ) ? 0x00 : 0x80;
            pchn -> mode = mode;
            pchn -> flags |= SND_PCM_FLG_NEUTRAL | SND_PCM_FLG_BUFFERS;
          }
      }
}

static unsigned int snd_pcm_oss_get_mode( snd_pcm_t *pcm, unsigned short flags )
{
  return (flags & 1) ? pcm -> playback.mode : pcm -> record.mode;
}

static void snd_pcm_oss_set_format( snd_pcm_t *pcm, unsigned short flags, unsigned int format )
{
  int idx;
  snd_pcm_channel_t *pchn;

#ifdef SND_PCM_DEBUG_BUFFERS
  printk( "format = 0x%x\n", format );
#endif
  for ( idx = 1; idx <= 2; idx++ )
    if ( flags & idx )
      {
        pchn = idx == 1 ? &pcm -> playback : &pcm -> record;
        pchn -> format = format;
      }
}

static unsigned int snd_pcm_oss_get_format( snd_pcm_t *pcm, unsigned short flags )
{
  return (flags & 1) ? pcm -> playback.format : pcm -> record.format;
}

static void snd_pcm_oss_set_rate( snd_pcm_t *pcm, unsigned short flags, unsigned int rate )
{
  int idx;
  snd_pcm_channel_t *pchn;

#ifdef SND_PCM_DEBUG_BUFFERS
  printk( "rate = %i\n", rate );
#endif
  for ( idx = 1; idx <= 2; idx++ )
    if ( flags & idx )
      {
        pchn = idx == 1 ? &pcm -> playback : &pcm -> record;
        if ( pchn -> rate != rate )
          {
            if ( idx == 1 )
              snd_pcm_oss_sync_playback( pcm );
             else
              snd_pcm_oss_sync_record( pcm );
            pchn -> rate = rate;
            pchn -> hw.compute_rate( pcm );
            pchn -> flags |= SND_PCM_FLG_BUFFERS; 
          }
    }
}

static unsigned int snd_pcm_oss_get_rate( snd_pcm_t *pcm, unsigned short flags )
{
  return (flags & 1) ? pcm -> playback.real_rate : pcm -> record.real_rate;
}

static int snd_pcm_oss_format( snd_pcm_t *pcm, unsigned short flags, int format )
{
  unsigned int new_mode;
  unsigned int new_format;

  if ( format != SND_PCM_FMT_QUERY )
    {
#if 0
      printk( "format = 0x%x\n", format );
#endif
      if ( !format )
        {
          new_format = ~snd_pcm_oss_get_format( pcm, flags );
          new_mode = snd_pcm_oss_get_mode( pcm, flags );
        }
       else
        {
          new_format = 0;
          if ( flags & SND_PCM_LFLG_PLAY ) {
            new_format = format & (pcm -> playback.hw.formats | SND_PCM_FMT_MU_LAW);
            if ( !new_format )			/* not supported format */
              new_format = SND_PCM_FMT_U8;	/* always supported (is it true?) */
          }
          if ( (flags & SND_PCM_LFLG_RECORD) &&
               ( !(pcm -> playback.flags & SND_PCM_FLG_MMAP) ||
                 (pcm -> record.flags & SND_PCM_FLG_MMAP) ) ) {
            if ( new_format )
              new_format &= pcm -> record.hw.formats | SND_PCM_FMT_MU_LAW;
             else
              new_format = format & (pcm -> record.hw.formats | SND_PCM_FMT_MU_LAW);
            if ( !new_format )			/* not supported format */
              new_format = SND_PCM_FMT_U8;	/* always supported */
          }
          new_mode = snd_pcm_oss_get_mode( pcm, flags ) & ~SND_PCM_MODE_TYPE;
          new_mode |= SND_PCM_MODE_VALID;
          if ( new_format & ( SND_PCM_FMT_MU_LAW | SND_PCM_FMT_A_LAW | SND_PCM_FMT_U8 | 
                              SND_PCM_FMT_U16_LE | SND_PCM_FMT_U16_BE ) )
            new_mode |= SND_PCM_MODE_U;
          if ( new_format & ( SND_PCM_FMT_S16_LE | SND_PCM_FMT_S16_BE | SND_PCM_FMT_U16_LE | 
                              SND_PCM_FMT_U16_BE | SND_PCM_FMT_IMA_ADPCM ) )
            new_mode |= SND_PCM_MODE_16;
          if ( new_format & SND_PCM_FMT_MU_LAW )
            new_mode |= SND_PCM_MODE_ULAW;
          if ( new_format & SND_PCM_FMT_A_LAW )
            new_mode |= SND_PCM_MODE_ALAW;
          if ( new_format & SND_PCM_FMT_IMA_ADPCM )
            new_mode |= SND_PCM_MODE_ADPCM;
        }
#ifdef SND_PCM_DEBUG_BUFFERS
      printk( "new_mode = 0x%x\n", new_mode );
#endif
      if ( new_mode != snd_pcm_oss_get_mode( pcm, flags ) )
        {
          snd_pcm_oss_set_format( pcm, flags, new_format );
          snd_pcm_oss_set_mode( pcm, flags, new_mode );
        }
    }

  return snd_pcm_oss_get_format( pcm, flags );
}

static int snd_pcm_oss_rate( snd_pcm_t *pcm, unsigned short flags, unsigned int rate )
{
  unsigned int max_rate;

  if ( rate > 0 ) 
    {
      max_rate = 48000;
      if ( flags & SND_PCM_LFLG_PLAY ) {
        if ( max_rate > pcm -> playback.hw.max_rate )
          max_rate = pcm -> playback.hw.max_rate;
      }
      if ( flags & SND_PCM_LFLG_RECORD ) {
        if ( max_rate > pcm -> record.hw.max_rate )
          max_rate = pcm -> record.hw.max_rate;
      }
      if ( rate > max_rate ) rate = max_rate;
      snd_pcm_oss_set_rate( pcm, flags, rate );
    }
   else
    rate = snd_pcm_oss_get_rate( pcm, flags );

  return rate;
}

static int snd_pcm_oss_set_channels( snd_pcm_t *pcm, unsigned short flags, int channels )
{
  snd_pcm_channel_t *pchn;

#if 0
  printk( "set channels = %i\n", channels );
#endif
  if ( channels >= 0 )
    {
      if ( channels < 1 || channels > 32 ) return -EINVAL;
      if ( flags & SND_PCM_LFLG_PLAY ) {
        pchn = &pcm -> playback;
        if ( channels > pchn -> hw.max_voices ) return -EINVAL;
        snd_pcm_oss_sync_playback( pcm );
        pchn -> voices = channels;
        pchn -> hw.compute_rate( pcm );
        pchn -> flags |= SND_PCM_FLG_BUFFERS;
      }
      if ( flags & SND_PCM_LFLG_RECORD ) {
        pchn = &pcm -> record;
        if ( channels > pchn -> hw.max_voices ) return -EINVAL;
        snd_pcm_oss_sync_record( pcm );
        pchn -> voices = channels;
        pchn -> hw.compute_rate( pcm );
        pchn -> flags |= SND_PCM_FLG_BUFFERS;
      }
    }
   else
    {
      if ( flags & SND_PCM_LFLG_PLAY )
        channels = pcm -> playback.voices;
      if ( flags & SND_PCM_LFLG_RECORD )
        channels = pcm -> record.voices;
    }
  return channels;
}

/*
 * This is probably wrong implementation.
 * Now is better way trigger out appropriate channel
 * and reset head, tail & used variables (drain).
 */

static int snd_pcm_oss_reset( snd_pcm_t *pcm, unsigned short flags )
{
  int idx;
  snd_pcm_channel_t *pchn;

#ifdef SND_PCM_DEBUG_BUFFERS
  printk( "pcm_reset!!!\n" );
#endif
  for ( idx = 1; idx <= 2; idx++ )
    if ( flags & idx )
      {
        pchn = idx == 1 ? &pcm -> playback : &pcm -> record;
        pchn -> flags |= SND_PCM_FLG_ABORT;	/* drain buffers only */
        if ( idx == 1 )
          snd_pcm_oss_sync_playback( pcm );
         else
          snd_pcm_oss_sync_record( pcm );
#if 0
        pchn -> requested_block_size =
        pchn -> requested_blocks =
        pchn -> requested_subdivision = 0;
        pchn -> processed_bytes = 0;
        pchn -> interrupts = 0;
        pchn -> flags |= SND_PCM_FLG_BUFFERS;
#endif
        pchn -> flags &= ~SND_PCM_FLG_ABORT;
      }
  return 0;
}

static int snd_pcm_oss_get_trigger( snd_pcm_t *pcm, unsigned short flags )
{
  unsigned long iflags;
  int result;

  result = 0;
  snd_spin_lock( pcm, playback, &iflags );
  if ( ( pcm -> playback.flags & SND_PCM_FLG_TRIGGER ) && ( flags & SND_PCM_LFLG_PLAY ) )
    result |= SND_PCM_ENABLE_PLAYBACK;
  snd_spin_unlock( pcm, playback, &iflags );
  snd_spin_lock( pcm, record, &iflags );
  if ( ( pcm -> record.flags & SND_PCM_FLG_TRIGGER ) && ( flags & SND_PCM_LFLG_RECORD ) )
    result |= SND_PCM_ENABLE_RECORD;
  snd_spin_unlock( pcm, record, &iflags );
#if 0
  printk( "get trigger = 0x%x\n", result );
#endif
  return result;
}

static int snd_pcm_oss_set_trigger( snd_pcm_t *pcm, unsigned short flags, int trigger )
{
  snd_card_t *card;
  snd_pcm_channel_t *pchn;

#if 0
  printk( "set trigger = 0x%x\n", trigger );
#endif
  card = pcm -> card;
  if ( flags & SND_PCM_LFLG_PLAY ) {
    pchn = &pcm -> playback;
    if ( trigger & SND_PCM_ENABLE_PLAYBACK ) {
      pchn -> flags |= SND_PCM_FLG_ENABLE;
      if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER) ) {
        if ( pchn -> flags & SND_PCM_FLG_BUFFERS )
          snd_pcm_oss_compute_blocks( pcm, SND_PCM_PLAYBACK );
        if ( pchn -> flags & SND_PCM_FLG_MMAP ) {
          if ( pchn -> hw.flags & SND_PCM_HW_8BITONLY ) {
            if ( pchn -> mode & SND_PCM_MODE_16 ) return -ENXIO;
          }
          if ( pchn -> hw.flags & SND_PCM_HW_16BITONLY ) {
            if ( !(pchn -> mode & SND_PCM_MODE_16) ) return -ENXIO;
          }
          if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER1) ) {	/* first trigger? */
            pchn -> hw.prepare( pcm,
                        	pchn -> buffer, pchn -> used_size,
            			0,				/* assuming that first block is always 0 */
            			pchn -> block_size );
          }
          pchn -> hw.trigger( pcm, 1 );
          pchn -> flags |= SND_PCM_FLG_TRIGGERA;
        } else {
          snd_pcm_oss_trigger_playback( pcm, pchn );
        }
      }
    } else {
      pchn -> flags &= ~SND_PCM_FLG_ENABLE;
      pchn -> flags |= SND_PCM_FLG_ABORT;
      snd_pcm_oss_sync_playback( pcm );
      pchn -> flags &= ~SND_PCM_FLG_ABORT;
    }
  } 
  if ( flags & SND_PCM_LFLG_RECORD ) {
    pchn = &pcm -> record;
    if ( trigger & SND_PCM_ENABLE_RECORD ) {
      pchn -> flags |= SND_PCM_FLG_ENABLE;
      if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER) ) {
        if ( pchn -> flags & SND_PCM_FLG_BUFFERS )
          snd_pcm_oss_compute_blocks( pcm, SND_PCM_RECORD );
        if ( pchn -> flags & SND_PCM_FLG_MMAP ) {
          if ( pchn -> hw.flags & SND_PCM_HW_8BITONLY ) {
            if ( pchn -> mode & SND_PCM_MODE_16 ) return -ENXIO;
          }
          if ( pchn -> hw.flags & SND_PCM_HW_16BITONLY ) {
            if ( !(pchn -> mode & SND_PCM_MODE_16) ) return -ENXIO;
          }
          if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER1) ) {	/* first trigger? */
            pchn -> hw.prepare( pcm,
            			pchn -> buffer, pchn -> used_size,
            			0,
            			pchn -> block_size );
          }
          pchn -> hw.trigger( pcm, 1 );
          pchn -> flags |= SND_PCM_FLG_TRIGGERA;
        } else {
          snd_pcm_oss_trigger_record( pcm, pchn );
        }
      }
    } else {
      pchn -> flags &= ~SND_PCM_FLG_ENABLE;
      pchn -> flags |= SND_PCM_FLG_ABORT;
      snd_pcm_oss_sync_playback( pcm );
      pchn -> flags &= ~SND_PCM_FLG_ABORT;
    }
  }
  return snd_pcm_oss_get_trigger( pcm, flags );
}

int snd_pcm_oss_trigger_sequencer( unsigned int devmask )
{
  int i;
  snd_pcm_t *pcm;
  
  for ( i = 0; i < snd_ecards_limit * 2; i++ ) {
    if ( ( devmask & (1 << i) ) == 0 ) continue; 
    pcm = snd_pcm_devices[ i >> 1 ];
    if ( !pcm ) continue;
    if ( !(pcm -> flags & SND_PCM_LFLG_PLAY) ) continue;
    snd_pcm_oss_set_trigger( pcm, SND_PCM_LFLG_PLAY, SND_PCM_ENABLE_PLAYBACK );
  }
  return 0;
}

/*
 *  GETOSPACE notes:
 *
 */

static int snd_pcm_oss_get_space( snd_pcm_t *pcm, unsigned short flags, int direction, struct snd_pcm_buffer_info *arg )
{
  unsigned long iflags;
  snd_pcm_channel_t *pchn;
  struct snd_pcm_buffer_info info;

  if ( !( flags & ( 1 << direction ) ) ) return -EIO;	/* bad file mode */
  if ( verify_area( VERIFY_WRITE, arg, sizeof( info ) ) ) return -EFAULT;
  pchn = direction == SND_PCM_PLAYBACK ? &pcm -> playback : &pcm -> record;
  if ( pchn -> flags & SND_PCM_FLG_BUFFERS )
    snd_pcm_oss_compute_blocks( pcm, direction );
  if ( direction == SND_PCM_PLAYBACK ) {
    snd_spin_lock( pcm, playback, &iflags );
  } else {
    snd_spin_lock( pcm, record, &iflags );
  }
  info.fragstotal = pchn -> blocks;
  info.fragsize = pchn -> block_size;
  if ( direction == SND_PCM_PLAYBACK ) {
    info.fragments = pchn -> blocks - pchn -> used;
    if ( pchn -> frag_size > 0 )
      info.fragments--;
  } else {
    info.fragments = pchn -> used;
  }
  if ( info.fragments < 0 ) info.fragments = 0;
  info.bytes = info.fragments * info.fragsize;
  if ( direction == SND_PCM_PLAYBACK ) {
    if ( pchn -> frag_size > 0 )
      info.bytes += pchn -> block_size - pchn -> frag_size;
  }
#if 0
  if ( direction == SND_PCM_PLAYBACK ) {
    if ( (pchn -> flags & SND_PCM_FLG_TRIGGER) || pchn -> used ) {
      info.bytes = pchn -> used_size + (pchn -> used * pchn -> block_size) + pchn -> frag_size;
    } else {
      info.bytes = pchn -> size;
    }
  } else {
    if ( (pchn -> flags & SND_PCM_FLG_TRIGGER) || pchn -> used ) {
      info.bytes = pchn -> used * pchn -> block_size;
    } else {
      info.bytes = 0;
    }
  }
#endif
  if ( direction == SND_PCM_PLAYBACK ) {
    snd_spin_unlock( pcm, playback, &iflags );
  } else {
    snd_spin_unlock( pcm, record, &iflags );
  }
#ifdef SND_PCM_DEBUG_BUFFERS
  printk( "space%i: frags = %i, total = %i, size = %i, bytes = %i (0x%x), frag_size = %i\n", direction, info.fragments, info.fragstotal, info.fragsize, info.bytes, info.bytes, pchn -> frag_size );
#endif
  pchn -> flags |= SND_PCM_FLG_ENABLE;
  copy_to_user( arg, &info, sizeof( info ) );
  return 0;
}

static int snd_pcm_oss_get_ptr( snd_pcm_t *pcm, unsigned short flags, int direction, struct snd_pcm_count_info *arg )
{
  unsigned long iflags;
  snd_pcm_channel_t *pchn;
  struct snd_pcm_count_info info;
  unsigned int size;

  if ( !( flags & ( 1 << direction ) ) ) return -EIO;	/* bad file mode */
  if ( verify_area( VERIFY_WRITE, arg, sizeof( info ) ) ) return -EFAULT;
  pchn = direction == SND_PCM_PLAYBACK ? &pcm -> playback : &pcm -> record;
  if ( pchn -> flags & SND_PCM_FLG_BUFFERS )
    snd_pcm_oss_compute_blocks( pcm, direction );
  if ( direction == SND_PCM_PLAYBACK ) {
    snd_spin_lock( pcm, playback, &iflags );
  } else {
    snd_spin_lock( pcm, record, &iflags );
  }
  info.bytes = pchn -> processed_bytes;
  if ( !(pchn -> flags & SND_PCM_FLG_TRIGGER) ) {
    info.ptr = pchn -> used_size;
  } else {
    info.ptr = pchn -> hw.pointer( pcm, pchn -> used_size );
    if ( !(pchn -> hw.flags & SND_PCM_HW_AUTODMA) ) {
      info.ptr += pchn -> tail * pchn -> block_size;
    }
    if ( info.ptr ) info.ptr--;
  }
  info.blocks = pchn -> used;
  if ( pchn -> flags & SND_PCM_FLG_MMAP ) {
    info.bytes += info.ptr;
    info.blocks = pchn -> interrupts;
    pchn -> interrupts = 0;
  } else {
    if ( !info.bytes ) {
      size = pchn -> size;
      if ( pchn -> hw.flags & SND_PCM_HW_8BITONLY ) {
        if ( pchn -> mode & SND_PCM_MODE_16 ) size <<= 1;
      }
      if ( pchn -> hw.flags & SND_PCM_HW_16BITONLY ) {
        if ( !(pchn -> mode & SND_PCM_MODE_16) ) size >>= 1;
      }
      info.bytes = info.ptr = size;
    }
    info.bytes += info.ptr % pchn -> block_size;
    info.blocks = pchn -> used;
  }
#if 0
  printk( "ptr: flags = 0x%x, bytes = %i, ptr = %i, blocks = %i\n", pchn -> flags, info.bytes, info.ptr, info.blocks );
#endif
  if ( direction == SND_PCM_PLAYBACK ) {
    snd_spin_unlock( pcm, playback, &iflags );
  } else {
    snd_spin_unlock( pcm, record, &iflags );
  }
  copy_to_user( arg, &info, sizeof( info ) );
  return 0;
}

static int snd_pcm_oss_get_mapbuf( snd_pcm_t *pcm, unsigned short flags, int direction, struct snd_pcm_buffer_description *arg )
{
  snd_pcm_channel_t *pchn;
  struct snd_pcm_buffer_description info;

  if ( !( flags & ( 1 << direction ) ) ) return -EIO;	/* bad file mode */
  if ( verify_area( VERIFY_WRITE, arg, sizeof( info ) ) ) return -EFAULT;
  pchn = direction == SND_PCM_PLAYBACK ? &pcm -> playback : &pcm -> record;
  info.buffer = (unsigned char *)pchn -> buffer;
  info.size = pchn -> size;
#ifdef SND_PCM_DEBUG_BUFFERS
  snd_printk( "mapbuf%i: size = %i\n", direction, info.size );
#endif
  copy_to_user( arg, &info, sizeof( info ) );
  return 0;
}

static int snd_pcm_oss_caps( snd_pcm_t *pcm, int flags )
{
  unsigned int result;
  
  result = /* SND_PCM_CAP_REALTIME | */ 
           SND_PCM_CAP_TRIGGER | SND_PCM_CAP_MMAP;
  if ( flags & SND_PCM_LFLG_PLAY ) {
    if ( pcm -> playback.hw.flags & SND_PCM_HW_BATCH )
      result |= SND_PCM_CAP_BATCH;
  } else {
    if ( pcm -> record.hw.flags & SND_PCM_HW_BATCH )
      result |= SND_PCM_CAP_BATCH;
  }
  if ( (pcm -> info_flags & SND_PCM_INFO_DUPLEX) && flags == SND_PCM_LFLG_BOTH )
    result |= SND_PCM_CAP_DUPLEX;
  result |= 0x0001;	/* revision - same as SB AWE 64 */
  return result;
}

static int snd_pcm_oss_nonblock( snd_pcm_t *pcm, int flags )
{
  if ( flags & SND_PCM_LFLG_PLAY )
    pcm -> playback.flags |= SND_PCM_FLG_NONBLK;
  if ( flags & SND_PCM_LFLG_RECORD )
    pcm -> record.flags |= SND_PCM_FLG_NONBLK;
  return 0;
}

static int snd_pcm_oss_open_card( snd_pcm_t *pcm, short minor, struct file *file )
{
  unsigned short flags;
  int res;

  flags = snd_pcm_file_flags( file -> f_flags );
#ifdef SND_PCM_DEBUG_BUFFERS
  snd_printk( "snd_pcm_open (1) - pcm = 0x%lx, minor = %i, flags = %i, pcm->flags = 0x%x\n", (long)pcm, minor, flags, pcm -> flags );
#endif
  if ( flags & pcm -> flags ) return -EBUSY;	/* channel(s) already used */
  if ( flags & SND_PCM_LFLG_PLAY ) {
    if ( !(pcm -> info_flags & SND_PCM_INFO_PLAYBACK) ) return -ENODEV;
    if ( flags == SND_PCM_LFLG_BOTH )
      if ( !(pcm -> info_flags & SND_PCM_INFO_DUPLEX) ) {
        file -> f_flags &= ~O_ACCMODE;
        file -> f_flags |= O_WRONLY;
        flags &= ~SND_PCM_LFLG_RECORD;
      }
  } else {
    if ( flags & SND_PCM_LFLG_RECORD )
      if ( !(pcm -> info_flags & SND_PCM_INFO_RECORD) ) return -ENODEV;
  }
  if ( flags & SND_PCM_LFLG_PLAY )
    snd_pcm_clear_channel( &pcm -> playback );
  if ( flags & SND_PCM_LFLG_RECORD )
    snd_pcm_clear_channel( &pcm -> record );
  pcm -> mask = SND_PCM_LFLG_BOTH;
  snd_pcm_oss_set_format( pcm, flags, 0 );
  snd_pcm_oss_set_mode( pcm, flags, 0 );
  switch ( minor & SND_MINOR_OSS_MASK ) {
    case SND_MINOR_OSS_AUDIO:
    case SND_MINOR_OSS_PCM1:
      snd_pcm_oss_format( pcm, flags, SND_PCM_FMT_MU_LAW );
      break;
    case SND_MINOR_OSS_PCM_8:
      snd_pcm_oss_format( pcm, flags, SND_PCM_FMT_U8 );
      break; 
    case SND_MINOR_OSS_PCM_16:
      snd_pcm_oss_format( pcm, flags, SND_PCM_FMT_S16_LE );
      break;
    default:
      snd_printd( "pcm: bad minor value\n" );
      return -EINVAL;
  }
  snd_pcm_oss_set_rate( pcm, flags, SND_PCM_DEFAULT_RATE );
  snd_pcm_oss_set_channels( pcm, flags, 1 );
  if ( flags & SND_PCM_LFLG_PLAY )
    {
      if ( ( res = pcm -> playback.hw.open( pcm ) ) < 0 )
        return res;
      pcm -> playback.flags |= SND_PCM_FLG_ENABLE;
      pcm -> playback.ack = snd_pcm_oss_interrupt_playback;
    }
  if ( flags & SND_PCM_LFLG_RECORD )
    {
      if ( ( res = pcm -> record.hw.open( pcm ) ) < 0 )
        {
          if ( flags & SND_PCM_LFLG_PLAY )
            {
              pcm -> playback.hw.close( pcm );
              pcm -> playback.flags &= ~SND_PCM_FLG_ENABLE;
            }
          return res;
        }
      pcm -> record.flags |= SND_PCM_FLG_ENABLE;
      pcm -> record.ack = snd_pcm_oss_interrupt_record;
    }
  pcm -> flags |= flags | ((flags & 3) << 2);

#if 0
  printk( "pcm open - done...\n" );
#endif

  return 0;
}

static void snd_pcm_oss_close_card( snd_pcm_t *pcm, struct file *file )
{
  unsigned short flags;
  
  if ( !pcm ) return;
  flags = snd_pcm_file_flags( file -> f_flags ) & pcm -> flags;
#if 0
  snd_printk( "snd_release_pcm - flags = %i\n", flags );
#endif
  if ( flags & SND_PCM_LFLG_PLAY ) {
    snd_pcm_oss_sync_playback( pcm );		/* synchronize playback */
    pcm -> playback.hw.trigger( pcm, 0 );
    pcm -> playback.hw.close( pcm );
    pcm -> flags &= ~(SND_PCM_LFLG_PLAY|SND_PCM_LFLG_OSS_PLAY);
  }
  if ( flags & SND_PCM_LFLG_RECORD ) {
    snd_pcm_oss_sync_record( pcm );		/* and record */
    pcm -> record.hw.trigger( pcm, 0 );
    pcm -> record.hw.close( pcm );
    pcm -> flags &= ~(SND_PCM_LFLG_RECORD|SND_PCM_LFLG_OSS_RECORD);
  }
#if 0
  printk( "release pcm: done\n" );
#endif
}

static int snd_pcm_oss_open( unsigned short minor, int cardnum, int device, struct file *file )
{
  int res;
  snd_pcm_t *pcm;

  pcm = snd_pcm_devices[ (cardnum * SND_PCM_DEVICES) + device ];
  if ( !pcm ) return -ENODEV;
  snd_mutex_down( pcm, open );
  if ( ( res = snd_pcm_oss_open_card( pcm, minor, file ) ) < 0 )
    {
      snd_mutex_up( pcm, open );
      return res;
    }
  MOD_INC_USE_COUNT;
  file -> private_data = pcm;
  pcm -> card -> use_inc( pcm -> card );
  snd_mutex_up( pcm, open );
  return 0;
}

static int snd_pcm_oss_release( unsigned short minor, int cardnum, int device, struct file *file )
{
  snd_pcm_t *pcm;
 
  if ( file -> private_data ) {
    pcm = (snd_pcm_t *)file -> private_data;
    snd_mutex_down( pcm, open );
    snd_pcm_oss_close_card( pcm, file );
    snd_mutex_up( pcm, open );
    pcm -> card -> use_dec( pcm -> card );
    file -> private_data = NULL;
  }
  MOD_DEC_USE_COUNT;
  return 0;
}

static int snd_pcm_oss_ioctl( struct file *file, unsigned int cmd, unsigned long arg )
{
  snd_pcm_t *pcm;
  snd_card_t *card;
  unsigned short flags;

  pcm = (snd_pcm_t *)file -> private_data;
  card = pcm -> card;
  if ( ( ( cmd >> 8 ) & 0xff ) == 'M' )	/* mixer ioctl - for OSS (grrr) compatibility */
    return snd_mixer_ioctl_card( card, file, cmd, arg );
  if ( ( ( cmd >> 8 ) & 0xff ) != 'P' ) return -EIO;
  flags = snd_pcm_file_flags( file -> f_flags ) & pcm -> mask;
#if 0
  if ( cmd != SND_PCM_IOCTL_OSS_GETPBKPTR )
    printk( "cmd = 0x%x, arg = 0x%x, flags = 0x%x\n", cmd, snd_ioctl_in( (long *)arg ), flags );
#endif
  switch ( cmd ) {
    case SND_PCM_IOCTL_OSS_RESET:
      return snd_pcm_oss_reset( pcm, flags );
    case SND_PCM_IOCTL_OSS_SYNC:
      return snd_pcm_oss_sync( pcm, flags );
    case SND_PCM_IOCTL_OSS_RATE:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_rate( pcm, flags, snd_ioctl_in( (long *)arg ) ) );
    case SND_PCM_IOCTL_OSS_GETRATE:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_get_rate( pcm, flags ) );
    case SND_PCM_IOCTL_OSS_STEREO:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_set_channels( pcm, flags, snd_ioctl_in( (long *)arg ) > 0 ? 2 : 1 ) - 1 );
    case SND_PCM_IOCTL_OSS_GETBLKSIZE:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_get_block_size( pcm, flags ) );
    case SND_PCM_IOCTL_OSS_FORMAT:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_format( pcm, flags, snd_ioctl_in( (long *)arg ) ) );
    case SND_PCM_IOCTL_OSS_GETFORMAT:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_get_format( pcm, flags ) );
    case SND_PCM_IOCTL_OSS_CHANNELS:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_set_channels( pcm, flags, snd_ioctl_in( (long *)arg ) ) );
    case SND_PCM_IOCTL_OSS_GETCHANNELS:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_set_channels( pcm, flags, -1 ) );
    case SND_PCM_IOCTL_OSS_FILTER:
    case SND_PCM_IOCTL_OSS_GETFILTER:
      return -EIO;
    case SND_PCM_IOCTL_OSS_POST:	/* wrong implementation */
      return snd_pcm_oss_sync( pcm, flags );
    case SND_PCM_IOCTL_OSS_SUBDIVIDE:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_set_subdivision( pcm, flags, snd_ioctl_in( (long *)arg ) ) );
    case SND_PCM_IOCTL_OSS_SETFRAGMENT:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_set_fragment( pcm, flags, snd_ioctl_in( (long *)arg ) ) );
    case SND_PCM_IOCTL_OSS_GETFORMATS:
      return snd_ioctl_out( (long *)arg, (flags & SND_PCM_LFLG_PLAY) ? pcm -> playback.hw.formats : pcm -> record.hw.formats );
    case SND_PCM_IOCTL_OSS_GETPBKSPACE:
    case SND_PCM_IOCTL_OSS_GETRECSPACE:
      return snd_pcm_oss_get_space( pcm, flags, cmd == SND_PCM_IOCTL_OSS_GETRECSPACE, (struct snd_pcm_buffer_info *)arg );
    case SND_PCM_IOCTL_OSS_NONBLOCK:
      return snd_pcm_oss_nonblock( pcm, flags );
    case SND_PCM_IOCTL_OSS_GETCAPS:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_caps( pcm, flags ) );
    case SND_PCM_IOCTL_OSS_GETTRIGGER:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_get_trigger( pcm, flags ) );
    case SND_PCM_IOCTL_OSS_SETTRIGGER:
      return snd_ioctl_out( (long *)arg, snd_pcm_oss_set_trigger( pcm, flags, snd_ioctl_in( (long *)arg ) ) );
    case SND_PCM_IOCTL_OSS_GETRECPTR:
    case SND_PCM_IOCTL_OSS_GETPBKPTR:
      return snd_pcm_oss_get_ptr( pcm, flags, cmd == SND_PCM_IOCTL_OSS_GETRECPTR ? SND_PCM_RECORD : SND_PCM_PLAYBACK, (struct snd_pcm_count_info *)arg );
    case SND_PCM_IOCTL_OSS_MAPRECBUFFER:
    case SND_PCM_IOCTL_OSS_MAPPBKBUFFER:
      return snd_pcm_oss_get_mapbuf( pcm, flags, cmd == SND_PCM_IOCTL_OSS_MAPRECBUFFER ? SND_PCM_RECORD : SND_PCM_PLAYBACK, (struct snd_pcm_buffer_description *)arg );
    case SND_PCM_IOCTL_OSS_SYNCRO:
      /* stop DMA now.. */
      return 0;
    case SND_PCM_IOCTL_OSS_DUPLEX:
      if ( snd_pcm_oss_caps( pcm, flags ) & SND_PCM_CAP_DUPLEX ) return 0;
      return -EIO;
    case SND_PCM_IOCTL_OSS_MASK:
      {
        int val;
      
        if ( snd_pcm_file_flags( file -> f_flags ) != SND_PCM_LFLG_BOTH )
          return -EIO;
        val = snd_ioctl_in( (long *)arg );
        val &= SND_PCM_LFLG_BOTH;
        if ( !val ) return -EINVAL;
        pcm -> mask = val;
        return 0;
      }
#ifdef SNDCFG_DEBUG
    default:
      snd_printk( "pcm: unknown command = 0x%x\n", cmd ); 
#endif
  }
  return -EIO;
}

static long snd_pcm_oss_read( struct file *file, char *buf, long count )
{
  snd_pcm_t *pcm;

  pcm = (snd_pcm_t *)file -> private_data;
  if ( !pcm ) return -EIO;
  if ( !( snd_pcm_file_flags( file -> f_flags ) & SND_PCM_LFLG_RECORD ) ||
       !( pcm -> flags & SND_PCM_LFLG_RECORD ) ) return -EIO;
  return snd_pcm_oss_buffer_to_user( pcm, buf, count );
}

static long snd_pcm_oss_write( struct file *file, const char *buf, long count )
{
  snd_pcm_t *pcm;

  pcm = (snd_pcm_t *)file -> private_data;
  if ( !pcm ) return -EIO;
  if ( !( snd_pcm_file_flags( file -> f_flags ) & SND_PCM_LFLG_PLAY ) ||
       !( pcm -> flags & SND_PCM_LFLG_PLAY ) ) return -EIO;
  return snd_pcm_oss_user_to_buffer( pcm, buf, count );
}

#ifdef SND_POLL
static unsigned int snd_pcm_oss_poll( struct file *file, poll_table *wait )
{
  snd_pcm_t *pcm;
  unsigned long flags;
  unsigned int mask;
  snd_pcm_channel_t *record, *playback;
  unsigned short fflags;

  fflags = snd_pcm_file_flags( file -> f_flags );
  pcm = (snd_pcm_t *)file -> private_data;
  if ( !pcm ) return 0;

  record = &pcm -> record;
  if ( (fflags & SND_PCM_LFLG_RECORD) &&
       (record -> flags & SND_PCM_FLG_BUFFERS) )
    snd_pcm_oss_compute_blocks( pcm, SND_PCM_RECORD );
  playback = &pcm -> playback;
  if ( (fflags & SND_PCM_LFLG_PLAY) &&
       (playback -> flags & SND_PCM_FLG_BUFFERS) )
    snd_pcm_oss_compute_blocks( pcm, SND_PCM_PLAYBACK );
  
  if ( fflags & SND_PCM_LFLG_RECORD ) {
    snd_pcm_oss_trigger_record( pcm, record );
    snd_spin_lock( pcm, record_sleep, &flags );
    record -> flags |= SND_PCM_FLG_SLEEP;
    snd_poll_wait( file, pcm, record, wait );
    snd_spin_unlock( pcm, record_sleep, &flags );
  }
  if ( fflags & SND_PCM_LFLG_PLAY ) {
    snd_spin_lock( pcm, playback_sleep, &flags );
    playback -> flags |= SND_PCM_FLG_SLEEP;
    snd_poll_wait( file, pcm, playback, wait );
    snd_spin_unlock( pcm, playback_sleep, &flags );
  }

  mask = 0;
  if ( fflags & SND_PCM_LFLG_RECORD ) {
    if ( record -> flags & SND_PCM_FLG_MMAP ) {
      if ( record -> interrupts ) mask |= POLLIN | POLLRDNORM;
    } else {
      if ( record -> used ) mask |= POLLIN | POLLRDNORM;
    }
  }
  if ( fflags & SND_PCM_LFLG_PLAY ) {
    if ( playback -> flags & SND_PCM_FLG_MMAP ) {
      if ( playback -> interrupts ) mask |= POLLOUT | POLLWRNORM;
    } else {
      if ( playback -> used < playback -> blocks ) mask |= POLLOUT | POLLWRNORM;
    }
  }

  return mask;
}
#else
static int snd_pcm_oss_select( struct file *file, int sel_type, select_table *wait )
{
  int ok;
  snd_pcm_t *pcm;
  unsigned long flags;
  snd_pcm_channel_t *pchn;
  unsigned short fflags;

  fflags = snd_pcm_file_flags( file -> f_flags );
  pcm = (snd_pcm_t *)file -> private_data;
  if ( !pcm ) return -EIO;
  switch ( sel_type ) {
    case SEL_IN:
      if ( !(fflags & SND_PCM_LFLG_RECORD) ) return 0;
      pchn = &pcm -> record;
      if ( pchn -> flags & SND_PCM_FLG_BUFFERS )
        snd_pcm_oss_compute_blocks( pcm, SND_PCM_RECORD );
      snd_pcm_oss_trigger_record( pcm, pchn );
      snd_spin_lock( pcm, record_sleep, &flags );
      if ( pchn -> flags & SND_PCM_FLG_MMAP )
        ok = !pchn -> interrupts; 
       else
        ok = !pchn -> used;
      if ( ok )
        {
          pchn -> flags |= SND_PCM_FLG_SLEEP;
          snd_select_wait( pcm, record, wait );
          snd_spin_unlock( pcm, record_sleep, &flags );
          return 0;
        }
      pchn -> flags &= ~SND_PCM_FLG_SLEEP;
      snd_spin_unlock( pcm, record_sleep, &flags );
      return 1;
    case SEL_OUT:
      if ( !(fflags & SND_PCM_LFLG_PLAY) ) return 0;
      pchn = &pcm -> playback;
      if ( pchn -> flags & SND_PCM_FLG_BUFFERS )
        snd_pcm_oss_compute_blocks( pcm, SND_PCM_PLAYBACK );
      snd_spin_lock( pcm, playback_sleep, &flags );
      if ( pchn -> flags & SND_PCM_FLG_MMAP )
        ok = !pchn -> interrupts; 
       else
        ok = pchn -> used >= pchn -> blocks;
      if ( ok )
        {
          pchn -> flags |= SND_PCM_FLG_SLEEP;
          snd_select_wait( pcm, playback, wait );
          snd_spin_unlock( pcm, playback_sleep, &flags );
          return 0;
        }
      pchn -> flags &= ~SND_PCM_FLG_SLEEP;
      snd_spin_unlock( pcm, playback_sleep, &flags );
      return 1;
    case SEL_EX:
      break;
  }
  return 0;
}
#endif

static void snd_pcm_oss_vma_open( struct vm_area_struct *area )
{
  MOD_INC_USE_COUNT;
}
                                    
static void snd_pcm_oss_vma_close( struct vm_area_struct *area )
{
  snd_dma_notify_vma_close( area );
  MOD_DEC_USE_COUNT;
}
                                            
static struct vm_operations_struct snd_pcm_oss_vm_ops = {
  snd_pcm_oss_vma_open,		/* open */
  snd_pcm_oss_vma_close,	/* close */
  NULL,				/* unmap */
  NULL,				/* protect */
  NULL,				/* sync */
  NULL,				/* advise */
  NULL,				/* nopage */
  NULL,				/* wppage */
  NULL,				/* swapout */
  NULL,				/* swapin */
};
                            
static int snd_pcm_oss_mmap( struct inode *inode, struct file *file, struct vm_area_struct *vma )
{
  snd_pcm_t *pcm;
  unsigned short flags;
  snd_pcm_channel_t *pchn;
  unsigned long size;
  struct snd_stru_dma *pdma;

  pcm = (snd_pcm_t *)file -> private_data;
  if ( !(pcm -> info_flags & SND_PCM_INFO_MMAP) ) return -EIO;
  flags = snd_pcm_file_flags( file -> f_flags );
  if ( ( vma -> vm_flags & ( VM_READ | VM_WRITE ) ) == ( VM_READ | VM_WRITE ) )
    return -EINVAL;
  if ( vma -> vm_flags & VM_READ )
    {
      if ( !(flags & SND_PCM_LFLG_RECORD) ) return -EINVAL;
      pchn = &pcm -> record;
    }
   else
  if ( vma -> vm_flags & VM_WRITE )
    {
      if ( !(flags & SND_PCM_LFLG_PLAY) ) return -EINVAL;
      pchn = &pcm -> playback;
    }
   else
    return -EINVAL;
  pdma = pcm -> card -> dmas[ pchn -> dmanum ];
  if ( !pdma ) return -ENOENT;
  if ( vma -> vm_offset != 0 ) return -EINVAL;
  size = vma -> vm_end - vma -> vm_start;
  if ( size != pchn -> size && size != pchn -> used_size ) {
    snd_printk( "snd-pcm: wrong mmap() size 0x%x, should be 0x%x\n", (unsigned int)size, pchn -> size );
  }
  pchn -> mmap_size = size;
#if 0
  printk( "remap: to=0x%x from=0x%x size=0x%x\n", (int)vma -> vm_start, (int)virt_to_bus( pchn -> buffer ), (int)size );
#endif
  if ( remap_page_range( vma -> vm_start,
                         virt_to_bus( pchn -> buffer ), 
                         size,
                         vma -> vm_page_prot ) )
    return -EAGAIN;
  if ( vma -> vm_ops )
    return -EINVAL;		/* Hmm... shouldn't happen */
#ifndef LINUX_2_1
  vma -> vm_inode = inode;
  inode -> i_count++;
#else
  vma -> vm_file = file;
  file -> f_count++;
#endif
  vma -> vm_ops = &snd_pcm_oss_vm_ops;
  MOD_INC_USE_COUNT;
  pdma -> mmaped = 1;
  pdma -> vma = vma;
  pchn -> flags |= SND_PCM_FLG_MMAP;
  snd_pcm_fill_with_neutral( pcm, pchn );
#if 0
  printk( "mmap ok..\n" );
#endif
  return 0;
}

/*
 *  ENTRY functions
 */

static snd_minor_t snd_pcm_oss_reg = {
  "digital audio",
  
  NULL,				/* unregister */
  
  NULL,				/* lseek */
  snd_pcm_oss_read,		/* read */
  snd_pcm_oss_write,		/* write */
  snd_pcm_oss_open,		/* open */
  snd_pcm_oss_release,		/* release */
#ifdef SND_POLL
  snd_pcm_oss_poll,		/* poll */
#else
  snd_pcm_oss_select,		/* select */
#endif
  snd_pcm_oss_ioctl,		/* ioctl */
  snd_pcm_oss_mmap,		/* mmap */
};

static int snd_pcm_oss_register_minor( unsigned short native_minor )
{
  int card;

  card = (native_minor - SND_MINOR_PCM) >> 2;
  switch ( native_minor & (SND_PCM_DEVICES-1) ) {
    case 0:
      snd_register_minor( (card << 4) + SND_MINOR_OSS_PCM_8, &snd_pcm_oss_reg );
      snd_register_minor( (card << 4) + SND_MINOR_OSS_AUDIO, &snd_pcm_oss_reg );
      snd_register_minor( (card << 4) + SND_MINOR_OSS_PCM_16, &snd_pcm_oss_reg );
      break;
    case 1:
      snd_register_minor( (card << 4) + SND_MINOR_OSS_PCM1, &snd_pcm_oss_reg );
      break;
    default:
      return 0;	/* other devices aren't useable for OSS minor mapping scheme */
  }
  return 0;
}

static int snd_pcm_oss_unregister_minor( unsigned short native_minor )
{
  int card;

  card = (native_minor - SND_MINOR_PCM) >> 2;
  switch ( native_minor & (SND_PCM_DEVICES-1) ) {
    case 0:
      snd_unregister_minor( (card << 4) + SND_MINOR_OSS_PCM_8 );
      snd_unregister_minor( (card << 4) + SND_MINOR_OSS_AUDIO );
      snd_unregister_minor( (card << 4) + SND_MINOR_OSS_PCM_16 );
      break;
    case 1:
      snd_unregister_minor( (card << 4) + SND_MINOR_OSS_PCM1 );
      break;
    default:
      return 0;	/* other devices aren't useable for OSS minor mapping scheme */
  }
  return 0;
}

int init_module( void )
{
  unsigned long flags;
  int idx;

#ifndef LINUX_2_1
  register_symtab( NULL );	/* this modules doesn't exports anything */
#endif
  snd_cli( &flags );
  snd_pcm_oss_register = snd_pcm_oss_register_minor;
  snd_pcm_oss_unregister = snd_pcm_oss_unregister_minor;
  snd_sti( &flags );
  for ( idx = 0; idx < SND_CARDS * SND_PCM_DEVICES; idx++ ) {
    if ( snd_pcm_devices[ idx ] == NULL ) continue;
    snd_pcm_oss_register_minor( idx + SND_MINOR_PCM );
  }
  return 0;
}

void cleanup_module( void )
{
  unsigned long flags;
  int idx;
  
  for ( idx = 0; idx < SND_CARDS * SND_PCM_DEVICES; idx++ ) {
    if ( snd_pcm_devices[ idx ] == NULL ) continue;
    snd_pcm_oss_unregister_minor( idx + SND_MINOR_PCM );
  }
  snd_cli( &flags );
  snd_pcm_oss_register = NULL;
  snd_pcm_oss_unregister = NULL;
  snd_sti( &flags );
}
