/*
 *  Copyright (c) 1994/95 by Jaroslav Kysela (Perex soft)
 *  Routines for PCM (Voxware 3.00 compatible)
 */

#include "gus_dev.h"
#include "pcm.h"

#define VOX_FORMATS() ( !use_codec ? \
AFMT_MU_LAW | AFMT_U8 | AFMT_S8 | AFMT_S16_LE | AFMT_U16_LE : \
AFMT_MU_LAW | AFMT_A_LAW | AFMT_IMA_ADPCM | AFMT_U8 | AFMT_U16_LE | AFMT_U16_BE )

#define PCM_LFLG_NONE	0x0000
#define PCM_LFLG_PLAY	0x0001
#define PCM_LFLG_RECORD	0x0002

#define DMA1_BUF_SIZE	( ( dma1_buf_size >> 1 ) & ~0x3f )
#define DMA2_BUF_SIZE	( ( dma2_buf_size >> 1 ) & ~0x3f )

struct stru_pcm_buf pcm_playback;
struct stru_pcm_buf pcm_record;

unsigned int pcm_mode = 0;
unsigned int pcm_format = 0;
unsigned int pcm_rate = 0;
unsigned int pcm_size = 0;			/* buffer size */

static unsigned short pcm_flags = PCM_LFLG_NONE;

SLEEP_PREPARE( playback );
SLEEP_PREPARE( record );

/* tables */

static unsigned char ulaw_dsp[] = {
     0,    0,    0,    0,    0,    0,    0,    0, 
     0,    0,    0,    0,    0,    0,    0,    0, 
     0,    0,    0,    0,    0,    0,    0,    0, 
     0,    0,    0,    0,    0,    0,    0,    2, 
     5,    9,   13,   17,   21,   25,   29,   33, 
    37,   41,   45,   49,   53,   57,   61,   65, 
    68,   70,   72,   74,   76,   78,   80,   82, 
    84,   86,   88,   90,   92,   94,   96,   98, 
   100,  101,  102,  103,  104,  105,  106,  107, 
   108,  109,  110,  111,  112,  113,  114,  115, 
   115,  116,  116,  117,  117,  118,  118,  119, 
   119,  120,  120,  121,  121,  122,  122,  123, 
   123,  123,  124,  124,  124,  124,  125,  125, 
   125,  125,  126,  126,  126,  126,  127,  127, 
   127,  127,  127,  127,  128,  128,  128,  128, 
   128,  128,  128,  128,  128,  128,  128,  128, 
   255,  255,  255,  255,  255,  255,  255,  255, 
   255,  255,  255,  255,  255,  255,  255,  255, 
   255,  255,  255,  255,  255,  255,  255,  255, 
   255,  255,  255,  255,  255,  255,  255,  255, 
   252,  248,  244,  240,  236,  232,  228,  224, 
   220,  216,  212,  208,  204,  200,  196,  192, 
   189,  187,  185,  183,  181,  179,  177,  175, 
   173,  171,  169,  167,  165,  163,  161,  159, 
   157,  156,  155,  154,  153,  152,  151,  150, 
   149,  148,  147,  146,  145,  144,  143,  142, 
   142,  141,  141,  140,  140,  139,  139,  138, 
   138,  137,  137,  136,  136,  135,  135,  134, 
   134,  134,  133,  133,  133,  133,  132,  132, 
   132,  132,  131,  131,  131,  131,  130,  130, 
   130,  130,  130,  130,  129,  129,  129,  129, 
   129,  129,  129,  129,  128,  128,  128,  128, 
};

static unsigned char dsp_ulaw[] = {
    31,   31,   31,   32,   32,   32,   32,   33, 
    33,   33,   33,   34,   34,   34,   34,   35, 
    35,   35,   35,   36,   36,   36,   36,   37, 
    37,   37,   37,   38,   38,   38,   38,   39, 
    39,   39,   39,   40,   40,   40,   40,   41, 
    41,   41,   41,   42,   42,   42,   42,   43, 
    43,   43,   43,   44,   44,   44,   44,   45, 
    45,   45,   45,   46,   46,   46,   46,   47, 
    47,   47,   47,   48,   48,   49,   49,   50, 
    50,   51,   51,   52,   52,   53,   53,   54, 
    54,   55,   55,   56,   56,   57,   57,   58, 
    58,   59,   59,   60,   60,   61,   61,   62, 
    62,   63,   63,   64,   65,   66,   67,   68, 
    69,   70,   71,   72,   73,   74,   75,   76, 
    77,   78,   79,   81,   83,   85,   87,   89, 
    91,   93,   95,   99,  103,  107,  111,  119, 
   255,  247,  239,  235,  231,  227,  223,  221, 
   219,  217,  215,  213,  211,  209,  207,  206, 
   205,  204,  203,  202,  201,  200,  199,  198, 
   197,  196,  195,  194,  193,  192,  191,  191, 
   190,  190,  189,  189,  188,  188,  187,  187, 
   186,  186,  185,  185,  184,  184,  183,  183, 
   182,  182,  181,  181,  180,  180,  179,  179, 
   178,  178,  177,  177,  176,  176,  175,  175, 
   175,  175,  174,  174,  174,  174,  173,  173, 
   173,  173,  172,  172,  172,  172,  171,  171, 
   171,  171,  170,  170,  170,  170,  169,  169, 
   169,  169,  168,  168,  168,  168,  167,  167, 
   167,  167,  166,  166,  166,  166,  165,  165, 
   165,  165,  164,  164,  164,  164,  163,  163, 
   163,  163,  162,  162,  162,  162,  161,  161, 
   161,  161,  160,  160,  160,  160,  159,  159, 
};

#ifdef __i386__

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

extern inline void *memsetw( void *s, short c, size_t count )
{
  __asm__ (
           "cld\n"
           "rep\n\t"
           "stosw"
           : /* no output */
           :"ax" (c), "D" (s), "c" (count/2)
           :"ax", "cx", "di");
  return s;
}

#else

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

static void memsetw( void *s, short c, size_t count )
{
  while ( count-- > 0 ) 
    *((short *)s)++ = c;
}

#endif

/*
   user to dma
*/

int pcm_user_to_dma( char *buf, int count )
{
  int res, i, ncount;
  char *dbufl, *dbufr;

#if 0
  PRINTK( "pcm_user_to_dma: buf=0x%x, count=0x%x\n", (int)buf, count );
#endif
#if 0
  if ( pcm_playback.flags & 0x8000 ) return -EIO;
#endif
  if ( count <= 0 ) return 0;
  if ( VERIFY_AREA( VERIFY_READ, buf, count ) ) return -EACCES;
#if 0
  if ( count > pcm_playback.size )
    {
      PRINTK( "pcm_user_to_dma: count is over - %d (max = %d)\n",
              count, pcm_playback.size ); 
      count = pcm_playback.size;
    }
#endif
  res = 0;
  if ( pcm_mode & PCM_MODE_STEREO )
    {
      if ( pcm_mode & PCM_MODE_16 ) count &= ~3; else count &= ~1;
    }
   else
    if ( pcm_mode & PCM_MODE_16 ) count &= ~1;
  while ( count > 0 )
    {
      while ( pcm_playback.used >= 2 )
        {
          pcm_playback.flags |= PCM_FLG_SLEEP;
          SLEEP( playback, 10 * HZ );
          if ( ( pcm_playback.flags & PCM_FLG_SLEEP ) && TABORT( playback ) )
            {
              pcm_playback.flags |= PCM_FLG_ABORT;
              return res;
            }
          pcm_playback.flags &= ~PCM_FLG_SLEEP;
          if ( pcm_playback.used >= 2 && TIMEOUT( playback ) )
            {
              PRINTK( "pcm_user_to_dma: timeout, new block discarded\n" );
              return -EIO;
            }
        }
      ncount = pcm_playback.size - pcm_playback.tsize[ pcm_playback.active ];
      if ( ncount > count ) ncount = count;
#if 0
      PRINTK( "pcm_user_to_dma: middle ncount=0x%x\n", ncount );
#endif
      if ( !use_codec && ( pcm_mode & PCM_MODE_STEREO ) )
        {
          dbufl = pcm_playback.buf[ pcm_playback.active ] + 
                  ( pcm_playback.tsize[ pcm_playback.active ] >> 1 );
          dbufr = dbufl + ( pcm_playback.size >> 1 );
          if ( !( pcm_mode & PCM_MODE_16 ) )
            {
              for ( i = ncount >> 1; i > 0; i-- )	/* optimalize this */
                {
                  *dbufl++ = get_fs_byte( buf++ );
                  *dbufr++ = get_fs_byte( buf++ );
                }
              if ( pcm_mode & PCM_MODE_ULAW )
                {
                  dbufl = pcm_playback.buf[ pcm_playback.active ] +
                          ( pcm_playback.tsize[ pcm_playback.active ] >> 1 );
                  dbufr = dbufl + ( pcm_playback.size >> 1 );
                  translate_bytes( ulaw_dsp, dbufl, ncount >> 1  );
                  translate_bytes( ulaw_dsp, dbufr, ncount >> 1 );
                }
            }
           else
            {
              for ( i = ncount >> 2; i > 0; i--, dbufl += 2, dbufr += 2 )
                {
                  *(unsigned short *)dbufl = get_fs_word( buf ); buf += 2;
                  *(unsigned short *)dbufr = get_fs_word( buf ); buf += 2;
                }
            }
        }
       else
        {
          MEMCPY_FROMFS( pcm_playback.buf[ pcm_playback.active ] + 
                           pcm_playback.tsize[ pcm_playback.active ],
                         buf, 
                         ncount );
          if ( !use_codec && ( pcm_mode & PCM_MODE_ULAW ) )
            translate_bytes( ulaw_dsp, 
                             pcm_playback.buf[ pcm_playback.active ] + 
                               pcm_playback.tsize[ pcm_playback.active ],
                             ncount );
          buf += ncount;
        }
      count -= ncount;
      res += ncount;
      pcm_playback.tsize[ pcm_playback.active ] += ncount;
      if ( pcm_playback.tsize[ pcm_playback.active ] == pcm_playback.size )
        {
          pcm_playback.active ^= 1;
          pcm_playback.used++;		/* ok. new block is ready to transfer */
#if 0
          PRINTK( "add done (count = 0x%x, filled block = %d)\n", 
               pcm_playback.size, pcm_playback.active ^ 1 );
#endif
          if ( !use_codec )
            gf1_init_to_dma();
           else
            codec_init_to_dma();
        }
#if 0
      PRINTK( "pcm_user_to_dma: end count=0x%x\n", count );
#endif
    }
#if 0
  PRINTK( "pcm_user_to_dma: end\n" );
#endif
#if 0
  if ( pcm_playback.flags & 0x8000 ) return -EIO;
#endif
  return res;
}

/*
   dma to user
*/

int pcm_dma_to_user( char *buf, int count )
{
  char *res_buf;
  int res, ncount;

  if ( VERIFY_AREA( VERIFY_WRITE, buf, count ) ) return -EACCES;
  if ( !use_codec )
    gf1_init_from_dma();
   else
    codec_init_from_dma();
  res = count;
  res_buf = buf;
  while ( count > 0 )
    {
      while ( !pcm_record.used )
        {
          pcm_record.flags |= PCM_FLG_SLEEP;
          SLEEP( record, 10 * HZ );
          if ( ( pcm_record.flags & PCM_FLG_SLEEP ) && TABORT( record ) )
            {
              pcm_record.flags |= PCM_FLG_ABORT;
              return res;
            }
          pcm_record.flags &= ~PCM_FLG_SLEEP;
          if ( !pcm_record.used && TIMEOUT( record ) )
            {
              PRINTK( "pcm_dma_to_user: timeout, new block discarded\n" );
              return -EIO;
            }
        }
      ncount = count <= pcm_record.tsize[ pcm_record.active ] ?
      	       count : pcm_record.tsize[ pcm_record.active ];
#if 0
      PRINTK( "to user: count=0x%x, ncount=0x%x, active=%d\n", count, ncount, pcm_record.active );
#endif
      MEMCPY_TOFS( buf, 
                   pcm_record.buf[ pcm_record.active ] +
                     ( pcm_record.size - pcm_record.tsize[ pcm_record.active ] ),
                   ncount );
      buf += ncount;
      count -= ncount;
      pcm_record.tsize[ pcm_record.active ] -= ncount;
      if ( !pcm_record.tsize[ pcm_record.active ] )
        {
          pcm_record.used--;
          pcm_record.active ^= 1;
        }
    }
  if ( !use_codec && ( pcm_mode & PCM_MODE_ULAW ) )
    {
      if ( VERIFY_AREA( VERIFY_READ, buf, count ) ) return -EACCES;
      translate_bytes( dsp_ulaw, res_buf, res );
    }
  return res;
}

/*
   synchronize playback
*/

static void pcm_sync_playback( void )
{
  if ( pcm_playback.used < 2 && pcm_playback.tsize[ pcm_playback.active ] > 0 )
    {
      pcm_playback.active ^= 1;
      pcm_playback.used++;
    }
  if ( !use_codec )
    {
      gf1_init_to_dma();		/* for sure */
      gf1_done_to_dma();
    }
   else
    {
      codec_init_to_dma();
      
      while ( pcm_playback.used >= 2 )
        {
          pcm_playback.flags |= PCM_FLG_SLEEP;
          SLEEP( playback, HZ * 10 );
          pcm_playback.flags &= ~PCM_FLG_SLEEP;
        }
        
      if ( pcm_playback.used > 0 )
        {
          register short active;
        
          active = pcm_playback.active;
          if ( pcm_mode & PCM_MODE_16 )
            memsetw( pcm_playback.buf[ active ] + pcm_playback.tsize[ active ],
                     pcm_mode & PCM_MODE_U ? 0 : 	/* nonportable */
                       ( pcm_mode & PCM_MODE_BIG ? 0x80 : 0x8000 ),
                     pcm_playback.size - pcm_playback.tsize[ active ] );
           else
            memset( pcm_playback.buf[ active ] + pcm_playback.tsize[ active ],
                    pcm_mode & PCM_MODE_U ? 0 : 0x80,
                    pcm_playback.size - pcm_playback.tsize[ active ] );
        }
      codec_done_to_dma();
    }
}

static void pcm_sync_record( void )
{
  if ( !use_codec )
    gf1_done_from_dma();
   else
    codec_done_from_dma();
}

/*
   other things
*/

static void set_pcm_stru_dma1( struct stru_pcm_buf *pcm_buf )
{
  pcm_buf -> dma = gus_dma1;
  pcm_buf -> flags = PCM_FLG_NONE;
  pcm_buf -> size = DMA1_BUF_SIZE;
  pcm_buf -> buf[ 0 ] = dma1_buf;
  pcm_buf -> buf[ 1 ] = dma1_buf + pcm_buf -> size;
  pcm_buf -> tsize[ 0 ] = pcm_buf -> tsize[ 1 ] = 0;
  pcm_buf -> active = pcm_buf -> used = 0;
#if 0
  PRINTK( "dma1_buf=0x%x\npcm_playback.buf[ 0 ]=0x%x, pcm_playback.buf[ 1 ]=0x%x\n",
            (int)dma1_buf, (int)pcm_playback.buf[ 0 ], (int)pcm_playback.buf[ 1 ] );
#endif
}

static void set_pcm_stru_dma2( struct stru_pcm_buf *pcm_buf )
{
  pcm_buf -> dma = gus_dma2;
  pcm_buf -> flags = PCM_FLG_NONE;
  pcm_buf -> size = DMA2_BUF_SIZE;
  pcm_buf -> buf[ 0 ] = dma2_buf;
  pcm_buf -> buf[ 1 ] = dma2_buf + pcm_buf -> size;
  pcm_buf -> tsize[ 0 ] = pcm_buf -> tsize[ 1 ] = 0;
  pcm_buf -> active = pcm_buf -> used = 0;
}

/*
 * seems to be problems with large buffer and small freq for CODEC
 * try make buffer size maximaly for 1 sec
 */

static void codec_buffer_correction( void )
{
  if ( use_codec )		/* only for codec */
    {
      int bps;			/* bytes per second */
      
      bps = pcm_rate;
      if ( pcm_mode & PCM_MODE_16 ) bps <<= 1;
      if ( pcm_mode & PCM_MODE_STEREO ) bps <<= 1;
      if ( pcm_mode & PCM_MODE_ADPCM ) bps >>= 2;
      bps >>= 1;
      bps &= ~3;		/* dword align */
      /* now correction */
      if ( pcm_size < bps ) bps = pcm_size;
      pcm_playback.size = bps;
      pcm_playback.buf[ 1 ] = pcm_playback.buf[ 0 ] + bps;
      pcm_record.size = bps;
      pcm_record.buf[ 1 ] = pcm_record.buf[ 0 ] + bps;
    }
}

static int set_format( short minor, int format )
{
  unsigned int new_mode;
  unsigned int new_format;

  if ( format != AFMT_QUERY )
    {
      new_format = format & VOX_FORMATS();
      if ( new_format == 0 )
        {
          new_format = pcm_format;
          new_mode = pcm_mode;
        }
       else
        {
          new_mode = pcm_mode & ~PCM_MODE_TYPE;
          if ( new_format & ( AFMT_MU_LAW | AFMT_A_LAW | AFMT_U8 | 
                              AFMT_U16_LE | AFMT_U16_BE ) )
            new_mode |= PCM_MODE_U;
          if ( new_format & ( AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | 
                              AFMT_U16_BE | AFMT_IMA_ADPCM ) )
            new_mode |= PCM_MODE_16;
          if ( new_format & AFMT_MU_LAW )
            new_mode |= PCM_MODE_ULAW;
          if ( new_format & AFMT_A_LAW )
            new_mode |= PCM_MODE_ALAW;
          if ( new_format & AFMT_IMA_ADPCM )
            new_mode |= PCM_MODE_ADPCM;
        }
      if ( new_format != pcm_format )
        {
          pcm_sync_playback();		/* sync buffer, if change mode */
          pcm_sync_record();
          pcm_format = new_format;
          pcm_mode = new_mode;
          codec_buffer_correction();
        }
    }

  /* return pcm_format; */
  return pcm_mode & PCM_MODE_16 ? 16 : 8;
}

static int set_rate( int rate )
{
  int max_rate;

  if ( rate > 0 ) 
    {
      max_rate = !use_codec ? 44100 : 48000;
      if ( rate > max_rate ) rate = max_rate;
      if ( pcm_rate != rate ) 
        {
          pcm_sync_playback();
          pcm_sync_record();
        }
      pcm_rate = rate;
      codec_buffer_correction();
    }
   else
    rate = pcm_rate;

  return rate;
}

static int set_stereo( int stereo )
{
  short old_stereo = pcm_mode & PCM_MODE_STEREO;
  
  if ( stereo )
    pcm_mode |= PCM_MODE_STEREO;
   else
    pcm_mode &= ~PCM_MODE_STEREO;

  if ( old_stereo != ( pcm_mode & PCM_MODE_STEREO ) )
    {
      pcm_sync_playback();
      pcm_sync_record();
    }
    
  codec_buffer_correction();

  return pcm_mode & PCM_MODE_STEREO ? 1 : 0;
}

int gus_open_pcm( short minor, unsigned short f_flags )
{
  unsigned short file_mode;

  if ( pcm_flags != PCM_FLG_NONE ) return -EBUSY;	/* already used */
  switch ( minor ) {
    case GUS_MINOR_AUDIO:
      pcm_mode = PCM_MODE_ULAW;
      pcm_format = AFMT_MU_LAW;
      break;
    case GUS_MINOR_PCM_8:
      pcm_mode = PCM_MODE_U;
      pcm_format = AFMT_U8;
      break; 
    case GUS_MINOR_PCM_16:
      pcm_mode = PCM_MODE_U | PCM_MODE_16;
      pcm_format = AFMT_U16_LE;
      break;
    default:
      PRINTK( "pcm: bad minor value\n" );
      return -EINVAL;
  }
  pcm_rate = PCM_DEFAULT_RATE;
  file_mode = f_flags & O_ACCMODE;
  if ( file_mode == O_RDWR && gus_dma1 == gus_dma2 ) 
    return -EIO;
  if ( !use_codec )
    {
      if ( gf1_mode != GF1_MODE_NONE )
        return -EBUSY;		/* GF1 synthesizer is busy */

      if ( ( file_mode == O_WRONLY || file_mode == O_RDWR ) && 
           ( dma1_lock & WK_LOCK ) )
        return -EBUSY;		/* DMA channel isn't free */
      if ( ( file_mode == O_RDONLY || file_mode == O_RDWR ) && 
           ( dma2_lock & WK_LOCK ) )
        return -EBUSY;		/* DMA channel isn't free */
      
      if ( file_mode == O_WRONLY || file_mode == O_RDWR )
        {
          set_pcm_stru_dma1( &pcm_playback );
          dma1_owner = "gf1 pcm - playback";
          dma1_lock = WK_LOCK;
          pcm_flags |= PCM_LFLG_PLAY;
        }

      if ( file_mode == O_RDONLY || file_mode == O_RDWR )
        {
          set_pcm_stru_dma2( &pcm_record );
          dma2_owner = "gf1 pcm - record";
          dma2_lock = WK_LOCK;
          pcm_flags |= PCM_LFLG_RECORD;
        }
    }
   else
    {
      if ( ( file_mode == O_WRONLY || file_mode == O_RDWR ) && 
           ( dma2_lock & WK_LOCK ) )
        return -EBUSY;		/* DMA channel isn't free */
      if ( ( file_mode == O_RDONLY || file_mode == O_RDWR ) && 
           ( dma1_lock & WK_LOCK ) )
        return -EBUSY;		/* DMA channel isn't free */

      if ( file_mode == O_WRONLY || file_mode == O_RDWR )
        {
          set_pcm_stru_dma2( &pcm_playback );
          dma2_owner = "codec pcm - playback";
          dma2_lock = WK_LOCK;
          pcm_flags |= PCM_LFLG_PLAY;
        }

      if ( file_mode == O_RDONLY || file_mode == O_RDWR )
        {
          set_pcm_stru_dma1( &pcm_record );
          dma1_owner = "codec pcm - record";
          dma1_lock = WK_LOCK;
          pcm_flags |= PCM_LFLG_RECORD;
        }
        
      codec_buffer_correction();
    }
  pcm_size = DMA1_BUF_SIZE;
  if ( gf1_mode == GF1_MODE_NONE )
    if ( gus_reset( 2 ) ) return -EIO;	/* we need only 2 voices for stereo sound */
  if ( codec_mode == CODEC_MODE_NONE ) codec_open();
  if ( !use_codec )
    gf1_mode = GF1_MODE_PCM;
   else
    codec_mode = CODEC_MODE_USED;
  MOD_INC_USE_COUNT;
  return 0;
}

void gus_release_pcm( short minor, unsigned short f_flags )
{
  pcm_sync_playback();		/* synchronize playback */
  pcm_sync_record();		/* and record */
  if ( !use_codec )
    {
      if ( pcm_flags & PCM_LFLG_PLAY ) { dma1_lock = 0; dma1_owner = NULL; }
      if ( pcm_flags & PCM_LFLG_RECORD ) { dma2_lock = 0; dma2_owner = NULL; }
      gf1_mode = GF1_MODE_NONE;
      gus_stop();		/* for sure */
    }
   else
    {
      if ( pcm_flags & PCM_LFLG_PLAY ) { dma2_lock = 0; dma2_owner = NULL; }
      if ( pcm_flags & PCM_LFLG_RECORD ) { dma1_lock = 0; dma1_owner = NULL; }
      codec_mode = CODEC_MODE_NONE;
      codec_close();		/* for sure */
    }
  pcm_flags = PCM_LFLG_NONE;
  MOD_DEC_USE_COUNT;
}

int gus_ioctl_pcm( short minor, unsigned int cmd, unsigned long arg )
{
  int i;

  switch ( cmd ) {
    case SOUND_PCM_WRITE_RATE:
      return IOCTL_OUT( arg, set_rate( IOCTL_IN( arg ) ) );
    case SOUND_PCM_READ_RATE:
      return IOCTL_OUT( arg, pcm_rate );
    case SNDCTL_DSP_STEREO:
      return IOCTL_OUT( arg, set_stereo( IOCTL_IN( arg ) ) );
    case SOUND_PCM_WRITE_CHANNELS:
      return IOCTL_OUT( arg, set_stereo( IOCTL_IN( arg ) - 1 ) + 1 );
    case SOUND_PCM_READ_CHANNELS:
      return IOCTL_OUT( arg, ( pcm_mode & PCM_MODE_STEREO ) ? 2 : 1 );
    case SOUND_PCM_GETFMTS:
      return IOCTL_OUT( arg, VOX_FORMATS() );
    case SOUND_PCM_SETFMT:
      return IOCTL_OUT( arg, set_format( minor, IOCTL_IN( arg ) ) );
    case SOUND_PCM_READ_BITS:
      return IOCTL_OUT( arg, pcm_format );
    case SNDCTL_DSP_GETBLKSIZE:
      return IOCTL_OUT( arg, pcm_size );
    case SOUND_PCM_SUBDIVIDE:
#if 0
      PRINTK( "pcm: SOUND_PCM_SUBDIVIDE ioctl cmd not yet implemented\n" );
#endif
      return IOCTL_OUT( arg, IOCTL_IN( arg ) );
    case SOUND_PCM_SETFRAGMENT:
#if 0
      PRINTK( "pcm: SOUND_PCM_SETFRAGMENT ioctl cmd not yet implemented (0x%x)\n", IOCTL_IN( arg ) );
#endif
      i = IOCTL_IN( arg ) & 0xffff;
      if ( i < 7 || i > 17 )
        return IOCTL_OUT( arg, -EINVAL );
      i = ( 1 << i );
      if ( i > DMA1_BUF_SIZE ) i = DMA1_BUF_SIZE;
      pcm_size = pcm_playback.size = pcm_record.size = i;
#if 0
      PRINTK( "pcm_size = %i\n", pcm_size );
#endif
      pcm_playback.buf[ 1 ] = pcm_playback.buf[ 0 ] + pcm_playback.size;
      pcm_record.buf[ 1 ] = pcm_record.buf[ 0 ] + pcm_record.size;
      codec_buffer_correction();
      return IOCTL_OUT( arg, IOCTL_IN( arg ) );
    case SOUND_PCM_SYNC:
    case SOUND_PCM_POST:
      pcm_sync_playback(); 
      pcm_sync_record();
      return 0;
    case SOUND_PCM_RESET:
      return 0;
    case SOUND_PCM_WRITE_FILTER:
      return IOCTL_OUT( arg, -EINVAL );
    case SOUND_PCM_READ_FILTER:
      return IOCTL_OUT( arg, -EINVAL ); 
    default:
      PRINTK( "pcm: unknown command = 0x%x\n", cmd ); 
  }
  return -EIO;
}

int gus_read_pcm( short minor, char *buf, int count )
{
  if ( ( pcm_flags & PCM_LFLG_PLAY ) && gus_dma1 == gus_dma2 ) return -EIO;
  return pcm_dma_to_user( buf, count );
}

int gus_write_pcm( short minor, char *buf, int count )
{
  if ( ( pcm_flags & PCM_LFLG_RECORD ) && gus_dma1 == gus_dma2 ) return -EIO;
  return pcm_user_to_dma( buf, count );
}
