/*
 *  Copyright (c) 1994/95 by Jaroslav Kysela (Perex soft)
 *  Routines for control of GF1 chip (PCM things)
 */

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

/* playback flags */

#define PFLG_NONE       	0x0000
#define PFLG_USED       	0x0001
#define PFLG_INIT       	0x0002
#define PFLG_LEFT       	0x0004
#define PFLG_RIGHT      	0x0008
#define PFLG_DMA		(PFLG_LEFT|PFLG_RIGHT)
#define PFLG_FLUSH     		0x0020
#define PFLG_DONE       	0x0040

/* record flags */

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

/* some macros */

#define GF1_TUSED( size )	( size / 5 )

/* some structures */

struct gf1_pcm_block {
  unsigned int ptr;
  unsigned int size;
  short end;
};

/* some variables */
 
static short gf1_pcm_voices;		/* 1 or 2 (mono or stereo) */
static short gf1_pflags = PFLG_NONE, gf1_rflags = RFLG_NONE;
static struct gf1_pcm_block gf1_pcm[ 2 ];
static unsigned int gf1_pcm_limit;
static volatile short gf1_play_active;
static volatile short gf1_pcm_used;	/* used pcm - 0, 1 or 2 */
static volatile short gf1_pcm_blk;	/* 0 or 1 */
static volatile short gf1_play_blk;	/* 0 or 1 */
static volatile short gf1_record_blk;
static unsigned char gf1_sampling_ctrl_reg;
static volatile short gf1_pcm_aflag;

/*
   Playback routines 
*/

static void gf1_init_transfer_left_pcm( void )
{
  gf1_pcm_used++;
  gf1_pflags &= ~( PFLG_LEFT | PFLG_RIGHT );
  gf1_pflags |= PFLG_LEFT;
  gf1_play_active = pcm_playback.active;
  if ( pcm_playback.used < 2 ) gf1_play_active ^= 1;
#if 0
  printk( "dma start (active block = %d; pcm block = %d)\n", gf1_play_active, gf1_pcm_blk );
  if ( gf1_play_blk == gf1_pcm_blk && gf1_pcm_used > 1 )
    {
      printk( "DMA unsync\n" );
      pcm_playback.flags |= 0x8000;
    }
#endif
  gf1_pcm[ gf1_pcm_blk ].ptr = 
    !gf1_pcm_blk ? 0 : pcm_playback.size >> ( gf1_pcm_voices - 1 );
  gf1_pcm[ gf1_pcm_blk ].size = 
    pcm_playback.tsize[ gf1_play_active ] >> ( gf1_pcm_voices - 1 );
  gf1_pcm[ gf1_pcm_blk ].end = 			/* forced end? */
    pcm_playback.tsize[ gf1_play_active ] < pcm_playback.size;
  pcm_playback.tsize[ gf1_play_active ] = 0;
#if 0
  if ( gf1_pcm[ gf1_pcm_blk ].end ) 
    PRINTK( "gf1_init_transfer_left_pcm: END!!! tsize=0x%x size=0x%x\n",
      	pcm_playback.tsize[ pcm_playback.active1 ],
    	pcm_playback.size );
#endif
  gf1_init_dma_transfer( 
    32 + gf1_pcm[ gf1_pcm_blk ].ptr,
    pcm_playback.buf[ gf1_play_active ],
    gf1_pcm[ gf1_pcm_blk ].size,
    pcm_mode & PCM_MODE_U,
    pcm_mode & PCM_MODE_16 );
#if 0
  PRINTK( "gf1_init_transfer_left_pcm: addr=0x%x, from=0x%x, size=0x%x\n",
  	32 + gf1_pcm[ gf1_pcm_blk ].ptr,
  	(int)pcm_playback.buf[ gf1_play_active ],
  	gf1_pcm[ gf1_pcm_blk ].size ) ;
#endif
}

static void gf1_init_transfer_right_pcm( void )
{
  gf1_pflags &= ~( PFLG_LEFT | PFLG_RIGHT );
  gf1_pflags |= PFLG_RIGHT;
  gf1_init_dma_transfer( 
    32 + gf1_pcm[ gf1_pcm_blk ].ptr + gf1_pcm_limit,
    pcm_playback.buf[ gf1_play_active ] + ( pcm_playback.size >> 1 ),
    gf1_pcm[ gf1_pcm_blk ].size,
    pcm_mode & PCM_MODE_U,
    pcm_mode & PCM_MODE_16 );
#if 0
  PRINTK( "gf1_init_transfer_right_pcm: addr=0x%x, from=0x%x, size=0x%x\n",
  	32 + gf1_pcm[ gf1_pcm_blk ].ptr + gf1_pcm_limit,
  	(int)pcm_playback.buf[ gf1_play_active ] + ( pcm_playback.size >> 1 ), 
  	gf1_pcm[ gf1_pcm_blk ].size );
#endif
}

void gf1_pcm_new_volume( void )
{
  unsigned short volume[ 2 ];
  short i;
  
  if ( ( gf1_pflags & PFLG_USED ) == 0 ) return;

  if ( gf1_pcm_voices == 1 )
    volume[ 0 ] = lvol_to_gf1_vol( ( current_mix[ MIX_PCM ].left + current_mix[ MIX_PCM ].right ) / 2 );
   else
    {
      volume[ 0 ] = lvol_to_gf1_vol( current_mix[ MIX_PCM ].left );
      volume[ 1 ] = lvol_to_gf1_vol( current_mix[ MIX_PCM ].right );
    }
  for ( i = 0; i < gf1_pcm_voices; i++ )
    {
      CLI();
      OUTB( i, GUSP( GF1PAGE ) );
      gus_write16( 9, volume[ i ] );
      STI();
    }
}

static void gf1_pcm_init( void )
{
  unsigned char voice_ctrl, ramp_ctrl;
  unsigned char volume[ 2 ];
  unsigned short rate = hz2_to_gf1_freq( pcm_rate * 2 );
  unsigned int current, begin, end;
  short i;

  if ( gf1_pcm_voices == 1 )
    volume[ 0 ] = lvol_to_gf1_vol( ( current_mix[ MIX_PCM ].left + current_mix[ MIX_PCM ].right ) / 2 ) >> 8;
   else
    {
      volume[ 0 ] = lvol_to_gf1_vol( current_mix[ MIX_PCM ].left ) >> 8;
      volume[ 1 ] = lvol_to_gf1_vol( current_mix[ MIX_PCM ].right ) >> 8;
    }

  voice_ctrl = pcm_mode & PCM_MODE_16 ? 0x24 : 0x20;	/* enable WAVE IRQ */
  ramp_ctrl = gf1_pcm[ gf1_play_blk ].end ? 0 : 4;	/* rollover & start */
  
  for ( i = 0; i < gf1_pcm_voices; i++ )
    {
      current = 32 + gf1_pcm[ gf1_play_blk ].ptr + i * gf1_pcm_limit;
      begin   = 32 + i * gf1_pcm_limit;
      end     = current + gf1_pcm[ gf1_play_blk ].size - 1;

      if ( !gf1_pcm[ gf1_play_blk ].end )
        {
          end -= GF1_TUSED( gf1_pcm[ gf1_play_blk ].size );
          gf1_pcm_aflag = 1;
        }
          
#if 0
      PRINTK( "init: begin=0x%x, end=0x%x, ctrl=0x%x\n", current, end, voice_ctrl );
      PRINTK( "pcm_rate: rate=%d, gf1=0x%x\n", pcm_rate, rate );
#endif
      CLI();
      OUTB( i, GUSP( GF1PAGE ) );	/* select voice */
      if ( pcm_mode & PCM_MODE_STEREO )	/* set panning */
        gus_write8( 0x0c, !i ? 3 : 15-3 );
       else
        gus_write8( 0x0c, 7 );		/* middle position */
      gus_write16( 1, rate );
      gus_write_addr( 2, begin, pcm_mode & PCM_MODE_16 );
      gus_write_addr( 4, end, pcm_mode & PCM_MODE_16 );
      gus_write16( 9, lvol_to_gf1_vol( 0 ) );
      gus_write_addr( 0x0a, begin, pcm_mode & PCM_MODE_16 );
      gus_write8( 6, 0x2f );		/* ramp rate */
      gus_write8( 7, 0x0e );		/* ramp start */
      gus_write8( 8, volume[ 0 ] );	/* ramp end */
      STI();
    }
  CLI();
  for ( i = 0; i < gf1_pcm_voices; i++ )
    {
      OUTB( i, GUSP( GF1PAGE ) );	/* select voice */
      gus_write8( 0x0d, ramp_ctrl );
      gus_write8( 0x00, voice_ctrl );
      gus_delay();
      gus_write8( 0x0d, ramp_ctrl );
      gus_write8( 0x00, voice_ctrl );
      voice_ctrl &= ~0x20;	/* disable IRQ for second voice */
    }
  STI();
}

static void gf1_pcm_end( void )
{
  unsigned char voice_ctrl, ramp_ctrl;
  unsigned int end;
  short i;

  voice_ctrl = pcm_mode & PCM_MODE_16 ? 0x24 : 0x20;	  /* enable WAVE IRQ */
  ramp_ctrl = !gf1_pcm[ gf1_play_blk ].end ? 0x07 : 0x03; /* rollover & stop */
    
  CLI();
  for ( i = 0; i < gf1_pcm_voices; i++ )
    {
      end     = 32 + gf1_pcm[ gf1_play_blk ].ptr +
                     gf1_pcm[ gf1_play_blk ].size +
                  i * gf1_pcm_limit - ( pcm_mode & PCM_MODE_16 ? 2 : 1 );
                  
      if ( !gf1_pcm[ gf1_play_blk ].end )
        {
          end -= GF1_TUSED( gf1_pcm[ gf1_play_blk ].size );
          gf1_pcm_aflag = 1;
        }
                  
      OUTB( i, GUSP( GF1PAGE ) );	/* select voice */
#if 0
      PRINTK( "end: end=0x%x, voice_ctrl=0x%x, ramp_ctrl=0x%x, begin=0x%x\n", end, voice_ctrl, ramp_ctrl, gus_read_addr( 2, pcm_mode & PCM_MODE_16 ) );
#endif
      gus_write_addr( 4, end, pcm_mode & PCM_MODE_16 );
      gus_write8( 0x00, voice_ctrl );
      gus_write8( 0x0d, ramp_ctrl );
      gus_delay();
      gus_write8( 0x00, voice_ctrl );
      gus_write8( 0x0d, ramp_ctrl );
      voice_ctrl &= ~0x20;
    }
  STI();
}

static void gf1_pcm_realy_end( void )
{
  unsigned char voice_ctrl, ramp_ctrl;
  unsigned int end;
  short i;

  voice_ctrl = pcm_mode & PCM_MODE_16 ? 0x24 : 0x20;	  /* enable WAVE IRQ */
  ramp_ctrl = 0x03;
  
  if ( !gf1_pcm[ gf1_play_blk ].end )
    {
      if ( gf1_play_blk ) 
        voice_ctrl |= 8;	/* enable loop */
       else
        ramp_ctrl |= 4;		/* enable rollover */
    }

  CLI();
  for ( i = 0; i < gf1_pcm_voices; i++ )
    {
      end     = 32 + gf1_pcm[ gf1_play_blk ].ptr +
                     gf1_pcm[ gf1_play_blk ].size +
                  i * gf1_pcm_limit - ( pcm_mode & PCM_MODE_16 ? 2 : 1 );
                  
      OUTB( i, GUSP( GF1PAGE ) );	/* select voice */
#if 0
      PRINTK( "realy_end: end=0x%x, voice_ctrl=0x%x, ramp_ctrl=0x%x, begin=0x%x\n", end, voice_ctrl, ramp_ctrl, gus_read_addr( 2, pcm_mode & PCM_MODE_16 ) );
#endif
      gus_write_addr( 4, end, pcm_mode & PCM_MODE_16 );
      gus_write8( 0x00, voice_ctrl );
      gus_write8( 0x0d, ramp_ctrl );
      gus_delay();
      gus_write8( 0x00, voice_ctrl );
      gus_write8( 0x0d, ramp_ctrl );
      voice_ctrl &= ~0x20;
    }
  STI();
}

static void pcm_update_gf1_voices( short dma_flag )
{
  static short use = 0;
  unsigned char status;

  if ( use++ > 0 )
    PRINTK( "pcm_update_gf1_voices: warning - entry (%d)\n", use );
  CLI();
  OUTB( 0, GUSP( GF1PAGE ) );		/* select voice 0 */
  status = gus_read8( 0 );
  STI();
  if ( status & 1 )			/* voice is stopped */
    gf1_pcm_init();			/* init it now */
   else
    if ( !dma_flag ) gf1_pcm_end();
#if 0
  if ( gf1_pcm[ gf1_play_blk ].size <= 0 )
    {
      printk( "zero block size reached (0)\n" );
      pcm_playback.flags |= 0x8000;
    }
  if ( pcm_playback.used < 2 && !dma_flag )
    printk( "PLAYBACK USED = %d\n", pcm_playback.used );
#endif
  if ( ( gf1_pcm_used < 2 ) &&
       !( gf1_pflags & PFLG_DMA ) &&
       pcm_playback.used > 0 )
    gf1_init_transfer_left_pcm();	/* next block to move */
  use--;
}

static void pcm_wave_gf1_voices( void )
{
  short i;

#if 0
  if ( !gf1_pcm_aflag )
    {
      /* PRINTK( "A " ); */
      OUTB( 1, GUSP( GF1PAGE ) );
      PRINTK( "** curr=0x%x, begin=0x%x, end=0x%x, ctrl=0x%x, ramp=0x%x (%d)\n", gus_read_addr( 0x0a, pcm_mode & PCM_MODE_16 ), gus_read_addr( 2, pcm_mode & PCM_MODE_16 ), gus_read_addr( 4, pcm_mode & PCM_MODE_16 ), gus_read8( 0 ), gus_read8( 0x0d ), gf1_play_blk );
    }
#endif
  if ( gf1_pcm_aflag )
    {
      gf1_pcm_aflag = 0;
      if ( gf1_pcm_used < 2 && !pcm_playback.used )
        {
          gf1_pcm[ gf1_play_blk ].end = 1;
#if 0
          PRINTK( "pcm_wave_gf1_voices: END!!!\n" );
#endif
        }
      gf1_pcm_realy_end();
      return;
    }
  if ( gf1_pcm[ gf1_play_blk ].end )		/* stop all voices */
    {
      gf1_play_blk ^= 1;
      gf1_pcm_used--;				/* now is block free */
      if ( gf1_pflags & PFLG_FLUSH ) { WAKEUP( playback ); }
      gf1_pflags &= PFLG_DMA;
      if ( gf1_pflags ) gf1_pflags |= PFLG_USED;
#if 0
      PRINTK( "stop - gf1_pflags=0x%x\n", gf1_pflags );
#endif
      CLI();
      for ( i = 0; i < gf1_pcm_voices; i++ )
        {
          OUTB( i, GUSP( GF1PAGE ) );		/* select voice */
          gus_write8(    0, 0x03 );
          gus_write8( 0x0d, 0x03 );
        }
      STI();
      if ( pcm_playback.flags & PCM_FLG_SLEEP )
        {
          pcm_playback.flags &= ~PCM_FLG_SLEEP;
          WAKEUP( playback );
        }
      return;
    }
  gf1_pcm[ gf1_play_blk ].ptr = 0;
  gf1_pcm[ gf1_play_blk ].size = 0;
  gf1_pcm[ gf1_play_blk ].end = 0;
  gf1_play_blk ^= 1;
  gf1_pcm_used--;				/* now is block free */
#if 0
  if ( gf1_pcm[ gf1_play_blk ].size <= 0 )
    {
      printk( "zero block size reached\n" );
      pcm_playback.flags |= 0x8000;
    }
#endif
#if 0
  PRINTK( "NEW!! gf1_play_blk: %d, gf1_pcm_used: %d\n", gf1_play_blk, gf1_pcm_used );
  PRINTK( "      ptr = 0x%x, size = 0x%x, end = %d\n", 
  				gf1_pcm[ gf1_play_blk ].ptr, 
  				gf1_pcm[ gf1_play_blk ].size,
  				gf1_pcm[ gf1_play_blk ].end );
#endif
  pcm_update_gf1_voices( 0 );
}

void gf1_pcm_interrupt( unsigned char status )
{
  short i;

#if 0
  PRINTK( "IRQ_GF1: status=0x%x\n", status );
#endif
#if 0
  OUTB( 1, GUSP( GF1PAGE ) );
  PRINTK( "IRQ_GF1: voice=0x%x, ramp=0x%x\n", gus_read8( 0 ), gus_read8( 0x0d ) ); 
#endif
  if ( !( gf1_pflags & PFLG_USED ) && !( gf1_rflags & RFLG_USED ) )
    {
      PRINTK( "pcm_interrupt: what's? GF1 IRQ?\n" );
      return;
    }
  if ( status & 0x80 )				/* DMA IRQ */
    {
      CLI();
      i = ( gus_read8( 0x49 ) & 0x40 ) && ( gf1_rflags & RFLG_USED );
      STI();
      if ( i )					/* ok. SAMPLING DMA IRQ */
        {
          if ( !( gf1_rflags & RFLG_STOP ) )
            {
              pcm_record.used++;
              pcm_record.tsize[ gf1_record_blk ] = pcm_record.size;
              gf1_record_blk ^= 1;
              gf1_init_rdma_transfer(
                pcm_record.buf[ gf1_record_blk ],
                pcm_record.size,
                gf1_sampling_ctrl_reg );
              if ( pcm_record.used > 2 )
                PRINTK( "pcm: warning! record overflow\n" );
              if ( pcm_record.flags & PCM_FLG_SLEEP )
                {
                  pcm_record.flags &= ~PCM_FLG_SLEEP;
                  WAKEUP( record );
                }
            }
           else
            {
              gf1_done_rdma_transfer();
              gf1_rflags = RFLG_NONE;
              WAKEUP( record )
            }
        }
      CLI();
      i = ( gus_read8( 0x41 ) & 0x40 ) && ( gf1_pflags & PFLG_USED );
      STI();
      if ( i )					/* ok. DRAM DMA IRQ */
        {
          gf1_done_dma_transfer();
          if ( ( gf1_pflags & PFLG_LEFT ) && ( pcm_mode & PCM_MODE_STEREO ) )
                                   /* transfer of left channel done */
            {
              gf1_init_transfer_right_pcm();
            }
           else
          if ( ( gf1_pflags & PFLG_RIGHT ) || 
               ( ( pcm_mode & PCM_MODE_STEREO ) == 0 && ( gf1_pflags & PFLG_LEFT ) ) )
            {
#if 0
              PRINTK( "dma done (free block %d)\n", gf1_play_active );
#endif
              gf1_pflags &= ~( PFLG_LEFT | PFLG_RIGHT );
              gf1_pcm_blk ^= 1;			/* switch PCM buffers */
              pcm_playback.used--;
              if ( pcm_playback.flags & PCM_FLG_SLEEP )
                {
                  pcm_playback.flags &= ~PCM_FLG_SLEEP;
                  WAKEUP( playback );
                }
              pcm_update_gf1_voices( 1 );
            }
           else
            PRINTK( "pcm_interrupt: what's? DMA?\n" );
        }
    }
  if ( status & 0x20 )				/* wavetable IRQ */
    {
      for ( i = 0; i < gf1_pcm_voices; i++ )
        {
          CLI();
          OUTB( i, GUSP( GF1PAGE ) );
          gus_write8( 0x00, gus_read8( 0x00 ) & ~(0x80|0x20) );
            					/* disable IRQ and loop */
          STI();
        }
      CLI();
      while ( ( gus_read8( 0x0f ) & 0xc0 ) != 0xc0 ) gus_delay();
      STI();
      pcm_wave_gf1_voices();
    }
  return;
}

void gf1_init_to_dma( void )
{
  if ( !pcm_playback.used ) return;
  if ( !( gf1_pflags & PFLG_USED ) )
    {
      gf1_pflags = PFLG_USED | PFLG_INIT;
      gf1_pcm_voices = pcm_mode & PCM_MODE_STEREO ? 2 : 1;
      gf1_pcm_used = 0;
      gf1_pcm_blk = gf1_play_blk = 0;
      gf1_pcm_limit = 32 + pcm_playback.size;
      gf1_init_transfer_left_pcm();
    }
   else 
  if ( ( gf1_pflags & PFLG_USED ) && 
       !( gf1_pflags & PFLG_DMA ) &&
       gf1_pcm_used < 2 )
    gf1_init_transfer_left_pcm();
}

void gf1_done_to_dma( void )
{
  if ( gf1_pflags & PFLG_USED )
    {
      gf1_pflags |= PFLG_FLUSH;
      while ( gf1_pflags & PFLG_FLUSH )
        {
          SLEEP( playback, HZ * 10 );
          if ( gf1_pflags & PFLG_USED )
            {
              if ( TABORT( playback ) ) 
                pcm_playback.flags |= PCM_FLG_ABORT;
               else
              if ( TIMEOUT( playback ) )
                PRINTK( "pcm: flush failed (playback)\n" );
               else
                continue;
              gus_stop();
              gf1_pflags = PFLG_NONE;
            }
        }
      gf1_pflags = PFLG_NONE;
      pcm_playback.used = 0;
    }
}

void gf1_init_from_dma()
{
  unsigned short tmp;

  if ( ( gf1_rflags & RFLG_USED ) == 0 )
    {
      gf1_record_blk = 0;
      pcm_record.used = 0;
      pcm_record.active = 0;
      gf1_rflags = RFLG_USED;
      tmp = ((9878400L>>4)/pcm_rate)-2;
      CLI();
      gus_write8( 0x48, tmp );		/* set sampling frequency */
      STI();
      gf1_sampling_ctrl_reg = 0x21;	/* IRQ at end enable & start */
      if ( pcm_mode & PCM_MODE_STEREO ) gf1_sampling_ctrl_reg |= 2;
      if ( gus_dma2 > 3 ) gf1_sampling_ctrl_reg |= 4;
      if ( pcm_mode & PCM_MODE_U ) gf1_sampling_ctrl_reg |= 0x80;
      gf1_init_rdma_transfer( pcm_record.buf[ 0 ],
                              pcm_record.size,
                              gf1_sampling_ctrl_reg );
    }
}

void gf1_done_from_dma( void )
{
  if ( gf1_rflags & RFLG_USED )
    {
      gf1_rflags |= RFLG_STOP;
      while ( gf1_rflags & RFLG_STOP )
        {
          SLEEP( record, HZ * 10 );
          if ( gf1_rflags & RFLG_USED )
            {
              if ( TABORT( record ) )
                pcm_record.flags |= PCM_FLG_ABORT;
               else
              if ( TIMEOUT( record ) )
                PRINTK( "pcm: flush failed (record)\n" );
               else
                continue;
              gf1_done_rdma_transfer();	/* for sure */
            }
        }
      gf1_rflags = RFLG_NONE;
      pcm_record.used = 0;
    }
}
