/*
 *  Routine for IRQ handling from GF1/InterWave chip
 *  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.
 *
 */

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

#define STAT_ADD( x ) while ( 0 ) { ; }

void snd_gus_interrupt( snd_gus_card_t *gus, unsigned char status )
{
#if 0
  PRINTK( "IRQ: status = 0x%x\n", status );
#endif
  if ( status & 0x01 ) {
    STAT_ADD( card -> gf1.interrupt_stat_midi_out );
    gus -> gf1.interrupt_handler_midi_out( gus );
  }
  if ( status & 0x02 ) {
    STAT_ADD( card -> gf1.interrupt_stat_midi_in );
    gus -> gf1.interrupt_handler_midi_in( gus );
  }
  if ( status & ( 0x20 | 0x40 ) ) {
    unsigned int i, already, _current_;
    unsigned char voice_status, voice;
    struct SND_STRU_GF1_VOICE_RANGE *range;
          
    already = 0;
    while ( ( ( voice_status = snd_gf1_i_read8( gus, SND_GF1_GB_VOICES_IRQ ) ) & 0xc0 ) != 0xc0 ) {
      voice = voice_status & 0x1f;
      _current_ = 1 << voice;
      if ( already & _current_ ) continue;	/* multi request */
      already |= _current_;			/* mark request */
      snd_gf1_select_voice( gus, voice );
#if 0
      printk( "voice = %i, voice_status = 0x%x, voice_verify = %i\n", voice, voice_status, INB( GUSP( gus, GF1PAGE ) ) );
#endif
      i = 0;
      range = gus -> gf1.voice_ranges;
      while ( i < SND_GF1_VOICE_RANGES ) {
        if ( voice >= range -> min && voice <= range -> max ) {
          if ( !(voice_status & 0x80) ) {	/* voice position IRQ */
            STAT_ADD( range -> interrupt_stat_wave );
            range -> interrupt_handler_wave( gus, voice );
          }
          if ( !(voice_status & 0x40) ) {	/* volume ramp IRQ */
            STAT_ADD( range -> interrupt_stat_volume );
            range -> interrupt_handler_volume( gus, voice );
          }
          goto __speedup1;
        }
        i++;
        range++;
      }
      STAT_ADD( gus -> gf1.interrupt_stat_voice_lost );
      snd_gf1_i_ctrl_stop( gus, SND_GF1_VB_ADDRESS_CONTROL );
      snd_gf1_i_ctrl_stop( gus, SND_GF1_VB_VOLUME_CONTROL );
      __speedup1:
    }
  }
  if ( status & 0x04 ) {
    STAT_ADD( gus -> gf1.interrupt_stat_timer1 );
    gus -> gf1.interrupt_handler_timer1( gus );
  }
  if ( status & 0x08 ) {
    STAT_ADD( gus -> gf1.interrupt_stat_timer2 );
    gus -> gf1.interrupt_handler_timer2( gus );
  }
  if ( status & 0x80 ) {
    if ( snd_gf1_i_look8( gus, SND_GF1_GB_DRAM_DMA_CONTROL ) & 0x40 ) {
      STAT_ADD( gus -> gf1.interrupt_stat_dma_write );
      gus -> gf1.interrupt_handler_dma_write( gus );
    }
    if ( snd_gf1_i_look8( gus, SND_GF1_GB_REC_DMA_CONTROL ) & 0x40 ) {
      STAT_ADD( gus -> gf1.interrupt_stat_dma_read );
      gus -> gf1.interrupt_handler_dma_read( gus );
    }
  }
}
