/*
 *  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 "midi.h"
#include "pcm.h"
#include "sb.h"

/*
 *
 */

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

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

/*
 *
 */

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

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

static int snd_sbdsp_midi_output_open( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  sbdsp_t *codec;
  
  codec = (sbdsp_t *)rmidi -> private_data;
  snd_spin_lock( codec, open8, &flags );
  if ( codec -> open8 ) {
    snd_spin_unlock( codec, open8, &flags );
    return -EBUSY;
  }
  codec -> open8 |= SB_OPEN_MIDI_OUTPUT;
  if ( !(codec -> open8 & SB_OPEN_MIDI_INPUT) ) {
    snd_spin_unlock( codec, open8, &flags );
    snd_sbdsp_reset( codec );				/* reset DSP */
  } else {
    snd_spin_unlock( codec, open8, &flags );
  }
  return 0;
}

static int snd_sbdsp_midi_input_close( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  sbdsp_t *codec;
  
  codec = (sbdsp_t *)rmidi -> private_data;
  snd_spin_lock( codec, open8, &flags );
  codec -> open8 &= ~(SB_OPEN_MIDI_INPUT|SB_OPEN_MIDI_TRIGGER);
  if ( !(codec -> open8 & SB_OPEN_MIDI_OUTPUT) ) {
    snd_spin_unlock( codec, open8, &flags );
    snd_sbdsp_reset( codec );				/* reset DSP */
  } else {
    snd_spin_unlock( codec, open8, &flags );
  }
  return 0;
}

static int snd_sbdsp_midi_output_close( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  sbdsp_t *codec;
  
  codec = (sbdsp_t *)rmidi -> private_data;
  snd_spin_lock( codec, open8, &flags );
  codec -> open8 &= ~SB_OPEN_MIDI_OUTPUT;
  if ( !(codec -> open8 & SB_OPEN_MIDI_INPUT) ) {
    snd_spin_unlock( codec, open8, &flags );
    snd_sbdsp_reset( codec );				/* reset DSP */
  } else {
    snd_spin_unlock( codec, open8, &flags );
  }
  return 0;
}

static void snd_sbdsp_midi_input_trigger( snd_rawmidi_t *rmidi, int up )
{
  unsigned long flags;
  sbdsp_t *codec;
  
  codec = (sbdsp_t *)rmidi -> private_data;
  snd_spin_lock( codec, open8, &flags );
  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_spin_unlock( codec, open8, &flags );  
}

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

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

/*
 *
 */
 
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;
}
