/*
 *  Copyright (c) by Jaroslav Kysela (Perex soft)
 *
 *
 *   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.
 *
 */

#include "driver.h"
#include "gus.h"

/*
 *  ok.. default interrupt handlers...
 */

static void snd_gf1_default_interrupt_handler_midi_out( snd_gus_card_t *gus )
{
  snd_gf1_uart_cmd( gus, gus -> gf1.uart_cmd &= ~0x20 );
}

static void snd_gf1_default_interrupt_handler_midi_in( snd_gus_card_t *gus )
{
  snd_gf1_uart_cmd( gus, gus -> gf1.uart_cmd &= ~0x80 );
}

static void snd_gf1_default_interrupt_handler_timer1( snd_gus_card_t *gus )
{
  snd_gf1_i_write8( gus, SND_GF1_GB_SOUND_BLASTER_CONTROL, gus -> gf1.timer_enabled &= ~4 );
}

static void snd_gf1_default_interrupt_handler_timer2( snd_gus_card_t *gus )
{
  snd_gf1_i_write8( gus, SND_GF1_GB_SOUND_BLASTER_CONTROL, gus -> gf1.timer_enabled &= ~8 );
}

static void snd_gf1_default_interrupt_handler_wave_and_volume( snd_gus_card_t *gus, int voice )
{
  snd_gf1_i_ctrl_stop( gus, 0x00 );
  snd_gf1_i_ctrl_stop( gus, 0x0d );
}

static void snd_gf1_default_interrupt_handler_dma_write( snd_gus_card_t *gus )
{
  snd_gf1_i_write8( gus, 0x41, 0x00 );
}

static void snd_gf1_default_interrupt_handler_dma_read( snd_gus_card_t *gus )
{
  snd_gf1_i_write8( gus, 0x49, 0x00 );
}

void snd_gf1_set_default_handlers( snd_gus_card_t *gus, unsigned int what )
{
  if ( what & SND_GF1_HANDLER_MIDI_OUT )
    gus -> gf1.interrupt_handler_midi_out = snd_gf1_default_interrupt_handler_midi_out;
  if ( what & SND_GF1_HANDLER_MIDI_IN )
    gus -> gf1.interrupt_handler_midi_in = snd_gf1_default_interrupt_handler_midi_in;
  if ( what & SND_GF1_HANDLER_TIMER1 )
    gus -> gf1.interrupt_handler_timer1 = snd_gf1_default_interrupt_handler_timer1;
  if ( what & SND_GF1_HANDLER_TIMER2 )
    gus -> gf1.interrupt_handler_timer2 = snd_gf1_default_interrupt_handler_timer2;
  if ( what & SND_GF1_HANDLER_RANGE )
    {
      unsigned int what1 = what & 0xffff;
      gus -> gf1.voice_ranges[ what1 ].interrupt_handler_wave =
      gus -> gf1.voice_ranges[ what1 ].interrupt_handler_volume =
        snd_gf1_default_interrupt_handler_wave_and_volume;
      gus -> gf1.voice_ranges[ what1 ].voices_change_start =
      gus -> gf1.voice_ranges[ what1 ].voices_change_stop =
      gus -> gf1.voice_ranges[ what1 ].volume_change = NULL;
    }
  if ( what & SND_GF1_HANDLER_DMA_WRITE )
    gus -> gf1.interrupt_handler_dma_write = snd_gf1_default_interrupt_handler_dma_write;
  if ( what & SND_GF1_HANDLER_DMA_READ )
    gus -> gf1.interrupt_handler_dma_read = snd_gf1_default_interrupt_handler_dma_read;
}

/*
 *
 */

static void snd_gf1_clear_regs( snd_gus_card_t *gus )
{
  unsigned long flags;

  snd_spin_lock( gus, reg, &flags );
  inb( GUSP( gus, IRQSTAT ) );
  snd_gf1_write8( gus, 0x41, 0 );		/* DRAM DMA Control Register */
  snd_gf1_write8( gus, 0x45, 0 );		/* Timer Control */
  snd_gf1_write8( gus, 0x49, 0 ); 		/* Sampling Control Register */
  snd_spin_unlock( gus, reg, &flags );
}

static void snd_gf1_look_regs( snd_gus_card_t *gus )
{
  unsigned long flags;

  snd_spin_lock( gus, reg, &flags );
  snd_gf1_look8( gus, 0x41 );	/* DRAM DMA Control Register */
  snd_gf1_look8( gus, 0x49 );	/* Sampling Control Register */
  inb( GUSP( gus, IRQSTAT ) );
  snd_gf1_read8( gus, 0x0f );	/* IRQ Source Register */
  snd_spin_unlock( gus, reg, &flags );
}

/*
 *  put selected GF1 voices to initial stage...
 */

void snd_gf1_smart_stop_voice( snd_gus_card_t *gus, unsigned short voice )
{
  unsigned long flags;
  
  snd_spin_lock( gus, reg, &flags );
  snd_gf1_select_voice( gus, voice );
#if 0
  printk( " -%i- smart stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16( gus, SND_GF1_VW_VOLUME ) );
#endif
  snd_gf1_ctrl_stop( gus, SND_GF1_VB_ADDRESS_CONTROL );
  snd_gf1_ctrl_stop( gus, SND_GF1_VB_VOLUME_CONTROL );
  snd_spin_unlock( gus, reg, &flags );
}

void snd_gf1_stop_voice( snd_gus_card_t *gus, unsigned short voice )
{
  unsigned long flags;
  
  snd_spin_lock( gus, reg, &flags );
  snd_gf1_select_voice( gus, voice );
#if 0
  printk( " -%i- stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16( gus, SND_GF1_VW_VOLUME ) );
#endif
  snd_gf1_ctrl_stop( gus, SND_GF1_VB_ADDRESS_CONTROL );
  snd_gf1_ctrl_stop( gus, SND_GF1_VB_VOLUME_CONTROL );
  snd_gf1_write8( gus, SND_GF1_VB_ACCUMULATOR, 0 );
  snd_spin_unlock( gus, reg, &flags );
#if 0
  snd_gf1_lfo_shutdown( gus, voice, ULTRA_LFO_VIBRATO );
  snd_gf1_lfo_shutdown( gus, voice, ULTRA_LFO_TREMOLO );
#endif
}

void snd_gf1_clear_voices( snd_gus_card_t *gus, unsigned short v_min, unsigned short v_max )
{
  unsigned long flags;
  unsigned int daddr;
  unsigned short i, w_16;

  daddr = gus -> gf1.default_voice_address << 4;
  for ( i = v_min; i <= v_max; i++ )
    {
#if 0
      if ( gus -> gf1.syn_voices )
        gus -> gf1.syn_voices[ i ].flags = ~VFLG_DYNAMIC;
#endif
      snd_spin_lock( gus, reg, &flags );
      snd_gf1_select_voice( gus, i );
      snd_gf1_ctrl_stop( gus, SND_GF1_VB_ADDRESS_CONTROL );	/* Voice Control Register = voice stop */
      snd_gf1_ctrl_stop( gus, SND_GF1_VB_VOLUME_CONTROL ); 	/* Volume Ramp Control Register = ramp off */
      if ( gus -> gf1.enh_mode )
        snd_gf1_write8( gus, SND_GF1_VB_MODE, gus -> gf1.memory ? 0x02 : 0x82 );	/* Deactivate voice */
      w_16 = snd_gf1_read8( gus, SND_GF1_VB_ADDRESS_CONTROL ) & 0x04;
      snd_gf1_write16( gus, SND_GF1_VW_FREQUENCY, 0x400 );
      snd_gf1_write_addr( gus, SND_GF1_VA_START, daddr, w_16 );
      snd_gf1_write_addr( gus, SND_GF1_VA_END, daddr, w_16 );
      snd_gf1_write8( gus, SND_GF1_VB_VOLUME_START, 0 );
      snd_gf1_write8( gus, SND_GF1_VB_VOLUME_END, 0 );
      snd_gf1_write8( gus, SND_GF1_VB_VOLUME_RATE, 0 );
      snd_gf1_write16( gus, SND_GF1_VW_VOLUME, 0 );
      snd_gf1_write_addr( gus, SND_GF1_VA_CURRENT, daddr, w_16 );
      snd_gf1_write8( gus, SND_GF1_VB_PAN, 7 );
      if ( gus -> gf1.enh_mode )
        {
          snd_gf1_write8( gus, SND_GF1_VB_ACCUMULATOR, 0 );
          snd_gf1_write16( gus, SND_GF1_VW_EFFECT_VOLUME, 0 );
          snd_gf1_write16( gus, SND_GF1_VW_EFFECT_VOLUME_FINAL, 0 );
        }
      snd_spin_unlock( gus, reg, &flags );
#if 0
      snd_gf1_lfo_shutdown( gus, i, ULTRA_LFO_VIBRATO );
      snd_gf1_lfo_shutdown( gus, i, ULTRA_LFO_TREMOLO );
#endif
    }
}

void snd_gf1_stop_voices( snd_gus_card_t *gus, unsigned short v_min, unsigned short v_max )
{
  unsigned long flags;
  short i, ramp_ok;
  unsigned short ramp_end;
  unsigned long time;

  if ( !in_interrupt() )	/* this can't be done in interrupt */
    {
      for ( i = v_min, ramp_ok = 0; i <= v_max; i++ )
        {
          snd_spin_lock( gus, reg, &flags );
          snd_gf1_select_voice( gus, i );
          ramp_end = snd_gf1_read16( gus, 9 ) >> 8;
          if ( ramp_end > SND_GF1_MIN_OFFSET )
            {
              ramp_ok++;
              snd_gf1_write8( gus, SND_GF1_VB_VOLUME_RATE, 20 );		/* ramp rate */
              snd_gf1_write8( gus, SND_GF1_VB_VOLUME_START, SND_GF1_MIN_OFFSET );	/* ramp start */
              snd_gf1_write8( gus, SND_GF1_VB_VOLUME_END, ramp_end );		/* ramp end */
              snd_gf1_write8( gus, SND_GF1_VB_VOLUME_CONTROL, 0x40 );		/* ramp down */
              if ( gus -> gf1.enh_mode )
                {
                  snd_gf1_delay( gus );
                  snd_gf1_write8( gus, SND_GF1_VB_VOLUME_CONTROL, 0x40 );
                }
            }
          snd_spin_unlock( gus, reg, &flags );
        }
      time = jiffies + (HZ/20);
      while ( (signed long)(time - jiffies) >= 0 )
        schedule();
    }
  snd_gf1_clear_voices( gus, v_min, v_max );
}

/*
 *  call this function only by start of driver
 */

int snd_gf1_start( snd_gus_card_t *gus )
{
  unsigned long flags;
  unsigned int i;

  snd_gf1_i_write8( gus, SND_GF1_GB_RESET, 0 );	/* reset GF1 */
  snd_delay( 16 );
  snd_gf1_i_write8( gus, SND_GF1_GB_RESET, 1 );	/* disable IRQ & DAC */
  snd_delay( 16 );
  snd_gf1_i_write8( gus, SND_GF1_GB_JOYSTICK_DAC_LEVEL, gus -> joystick_dac );

  snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_ALL );
  snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_RANGE | SND_GF1_VOICE_RANGE_SYNTH );
  snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_RANGE | SND_GF1_VOICE_RANGE_PCM );
  snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_RANGE | SND_GF1_VOICE_RANGE_EFFECT );

  snd_gf1_uart_cmd( gus, 0x03 );	/* huh.. this cleanup take me some time... */

  if ( gus -> gf1.enh_mode ) {		/* enhanced mode !!!! */
    snd_gf1_i_write8( gus, SND_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8( gus, SND_GF1_GB_GLOBAL_MODE ) | 0x01 );
    snd_gf1_i_write8( gus, SND_GF1_GB_MEMORY_CONTROL, 0x01 );
  }
  snd_gf1_clear_regs( gus );
  gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_SYNTH ].rvoices = 0;
  gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_PCM ].rvoices = 0;
  gus -> gf1.voice_ranges[ SND_GF1_VOICE_RANGE_EFFECT ].rvoices = 0;
  snd_gf1_reselect_active_voices( gus );
  snd_gf1_delay( gus );
  gus -> gf1.default_voice_address = gus -> gf1.memory > 0 ? 0 : 512 - 8;
  /* initialize LFOs & clear LFOs memory */
  if ( gus -> gf1.enh_mode && gus -> gf1.memory ) {
    gus -> gf1.hw_lfo = 1;
    gus -> gf1.default_voice_address += 1024;
  } else {
    gus -> gf1.sw_lfo = 1;
  }
#if 0
  snd_gf1_lfo_init( gus );
#endif
  if ( gus -> gf1.memory > 0 )
    for ( i = 0; i < 4; i++ )
      snd_gf1_poke( gus, gus -> gf1.default_voice_address + i, 0 );
  snd_gf1_clear_regs( gus );
  snd_gf1_clear_voices( gus, 0, 31 );
  snd_gf1_look_regs( gus );
  snd_delay( 16 );
  snd_gf1_i_write8( gus, SND_GF1_GB_RESET, 7 ); 	/* Reset Register = IRQ enable, DAC enable */
  snd_delay( 16 );
  snd_gf1_i_write8( gus, SND_GF1_GB_RESET, 7 );	/* Reset Register = IRQ enable, DAC enable */
  if ( gus -> gf1.enh_mode ) {			/* enhanced mode !!!! */
    snd_gf1_i_write8( gus, SND_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8( gus, SND_GF1_GB_GLOBAL_MODE ) | 0x01 );
    snd_gf1_i_write8( gus, SND_GF1_GB_MEMORY_CONTROL, 0x01 );
  }
  
  while ( ( snd_gf1_i_read8( gus, SND_GF1_GB_VOICES_IRQ ) & 0xc0 ) != 0xc0 );

  snd_spin_lock( gus, reg, &flags );
  outb( gus -> gf1.active_voice = 0, GUSP( gus, GF1PAGE ) );
  outb( gus -> mix_cntrl_reg, GUSP( gus, MIXCNTRLREG ) );
  snd_spin_unlock( gus, reg, &flags );

  snd_gf1_look_regs( gus );
  snd_gf1_mem_init( gus );

#if 0
  if ( gus -> pnp_flag )
    {
      if ( gus -> codec.playback_fifo_size > 0 )
       snd_gf1_i_write16( gus, SND_GF1_GW_FIFO_RECORD_BASE_ADDR, gus -> codec.playback_fifo_block -> ptr >> 8 );
      if ( gus -> codec.record_fifo_size > 0 )
       snd_gf1_i_write16( gus, SND_GF1_GW_FIFO_PLAY_BASE_ADDR, gus -> codec.record_fifo_block -> ptr >> 8 );
      snd_gf1_i_write16( gus, SND_GF1_GW_FIFO_SIZE, gus -> codec.interwave_fifo_reg );
    }
#endif

  return 0;
}

/*
 *  call this function only by shutdown of driver
 */

int snd_gf1_stop( snd_gus_card_t *gus )
{
  snd_gf1_i_write8( gus, 0x45, 0 );	/* stop all timers */
  snd_gf1_stop_voices( gus, 0, 31 );	/* stop all voices */
  snd_gf1_i_write8( gus, 0x4c, 1 );	/* disable IRQ & DAC */
  snd_gf1_mem_done( gus );
#if 0
  snd_gf1_lfo_done( gus );
#endif
  return 0;
}

/*
 *  standard open/close function
 */

static void snd_gf1_set_reset_flags( snd_gus_card_t *gus )
{
  unsigned short mode;
  unsigned char reset;
  
  mode = gus -> gf1.mode;
  reset = 1;				/* IRQ & DAC disable, no reset */
  if ( mode & (SND_GF1_MODE_ENGINE|SND_GF1_MODE_PCM) )
    reset |= 7;				/* IRQ & DAC enable */
  if ( mode & SND_GF1_MODE_TIMER )
    reset |= 5;				/* IRQ enable */
  snd_gf1_i_write8( gus, 0x4c, reset );
}
 
void snd_gf1_open( snd_gus_card_t *gus, unsigned short mode )
{
  if ( gus -> gf1.mode & mode ) return; /* already */
  gus -> gf1.mode |= mode;
  snd_gf1_set_reset_flags( gus );
}

void snd_gf1_close( snd_gus_card_t *gus, unsigned short mode )
{
  if ( !( gus -> gf1.mode & mode ) ) return;
  gus -> gf1.mode &= ~mode;
  snd_gf1_set_reset_flags( gus );
}
