/*
 *  Routine for IRQ handling from GF1/InterWave chip
 *  Copyright (c) by Jaroslav Kysela <perex@suse.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
	snd_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);
		}
	}
}
