/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for control of SoundBlaster cards - MIDI interface
 *
 *   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 "sb.h"

/*

 */

void snd_sbdsp_midi_interrupt(snd_rawmidi_t * rmidi)
{
	sbdsp_t *codec;
	int max = 64;
	char byte;

	codec = (sbdsp_t *) rmidi->private_data;
	if (!codec) {
		in8(SBP(codec, READ));	/* ack interrupt */
		return;
	}
	while (max-- > 0) {
		snd_mutex_lock(codec, midi_input);
		if (in8(SBP(codec, DATA_AVAIL) & 0x80)) {
			byte = in8(SBP(codec, READ));
			snd_mutex_unlock(codec, midi_input);
			rmidi->input.data(rmidi, &byte, 1);
		} else {
			snd_mutex_unlock(codec, midi_input);
		}
	}
}

/*

 */

static int snd_sbdsp_midi_input_open(snd_rawmidi_t * rmidi)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) rmidi->private_data;
	snd_mutex_lock(codec, open8);
	if (codec->open8) {
		snd_mutex_unlock(codec, open8);
		return -EBUSY;
	}
	codec->open8 |= SB_OPEN_MIDI_INPUT;
	if (!(codec->open8 & SB_OPEN_MIDI_OUTPUT)) {
		snd_mutex_unlock(codec, open8);
		snd_sbdsp_reset(codec);		/* reset DSP */
	} else {
		snd_mutex_unlock(codec, open8);
	}
	return 0;
}

static int snd_sbdsp_midi_output_open(snd_rawmidi_t * rmidi)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) rmidi->private_data;
	snd_mutex_lock(codec, open8);
	if (codec->open8) {
		snd_mutex_unlock(codec, open8);
		return -EBUSY;
	}
	codec->open8 |= SB_OPEN_MIDI_OUTPUT;
	if (!(codec->open8 & SB_OPEN_MIDI_INPUT)) {
		snd_mutex_unlock(codec, open8);
		snd_sbdsp_reset(codec);		/* reset DSP */
	} else {
		snd_mutex_unlock(codec, open8);
	}
	return 0;
}

static int snd_sbdsp_midi_input_close(snd_rawmidi_t * rmidi)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) rmidi->private_data;
	snd_mutex_lock(codec, open8);
	codec->open8 &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_TRIGGER);
	if (!(codec->open8 & SB_OPEN_MIDI_OUTPUT)) {
		snd_mutex_unlock(codec, open8);
		snd_sbdsp_reset(codec);		/* reset DSP */
	} else {
		snd_mutex_unlock(codec, open8);
	}
	return 0;
}

static int snd_sbdsp_midi_output_close(snd_rawmidi_t * rmidi)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) rmidi->private_data;
	snd_mutex_lock(codec, open8);
	codec->open8 &= ~SB_OPEN_MIDI_OUTPUT;
	if (!(codec->open8 & SB_OPEN_MIDI_INPUT)) {
		snd_mutex_unlock(codec, open8);
		snd_sbdsp_reset(codec);		/* reset DSP */
	} else {
		snd_mutex_unlock(codec, open8);
	}
	return 0;
}

static void snd_sbdsp_midi_input_trigger(snd_rawmidi_t * rmidi, int up)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) rmidi->private_data;
	snd_mutex_lock(codec, open8);
	if (up) {
		if (!(codec->open8 & SB_OPEN_MIDI_TRIGGER)) {
			snd_sbdsp_command(codec, SB_DSP_MIDI_INPUT_IRQ);
			codec->open8 |= SB_OPEN_MIDI_TRIGGER;
		}
	} else {
		if (codec->open8 & SB_OPEN_MIDI_TRIGGER) {
			snd_sbdsp_command(codec, SB_DSP_MIDI_INPUT_IRQ);
			codec->open8 &= ~SB_OPEN_MIDI_TRIGGER;
		}
	}
	snd_mutex_unlock(codec, open8);
}

static void snd_sbdsp_midi_output_write(snd_rawmidi_t * rmidi)
{
	sbdsp_t *codec;
	char byte;
	int max = 32;

	/* how big is Tx FIFO? */
	codec = (sbdsp_t *) rmidi->private_data;
	snd_mutex_lock(codec, open8);
	while (max-- > 0) {
		if (rmidi->output.data(rmidi, &byte, 1) != 1) {
			snd_mutex_unlock(codec, open8);
			return;
		}
		snd_sbdsp_command(codec, SB_DSP_MIDI_OUTPUT);
		snd_sbdsp_command(codec, byte);
	}
	snd_mutex_lock(codec, open8);
}

/*

 */

static struct snd_stru_rawmidi_direction_hw snd_sbdsp_midi_output =
{
	SND_RAWMIDI_HW_POLL,	/* flags */
	NULL,			/* private_data */
	NULL,			/* private_free */
	snd_sbdsp_midi_output_open,	/* open */
	snd_sbdsp_midi_output_close,	/* close */
	NULL,			/* trigger */
	{snd_sbdsp_midi_output_write},	/* io.write */
	NULL,			/* abort */
};

static struct snd_stru_rawmidi_direction_hw snd_sbdsp_midi_input =
{
	0,			/* flags */
	NULL,			/* private_data */
	NULL,			/* private_free */
	snd_sbdsp_midi_input_open,	/* open */
	snd_sbdsp_midi_input_close,	/* close */
	snd_sbdsp_midi_input_trigger,	/* trigger */
	{NULL},			/* io.read */
	NULL,			/* abort */
};

snd_rawmidi_t *snd_sbdsp_midi_new_device(snd_card_t * card, snd_pcm_t * pcm)
{
	sbdsp_t *codec;
	snd_rawmidi_t *rmidi;

	codec = (sbdsp_t *) pcm->private_data;
	if ((rmidi = snd_rawmidi_new_device(card, "SB8 MIDI")) == NULL)
		return NULL;
	strcpy(rmidi->name, "SB8 MIDI");
	memcpy(&rmidi->output.hw, &snd_sbdsp_midi_output, sizeof(snd_sbdsp_midi_output));
	memcpy(&rmidi->input.hw, &snd_sbdsp_midi_input, sizeof(snd_sbdsp_midi_input));
	rmidi->info_flags |= SND_RAWMIDI_INFO_OUTPUT | SND_RAWMIDI_INFO_INPUT | SND_RAWMIDI_INFO_DUPLEX;
	rmidi->private_data = codec;
	return rmidi;
}
