/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for the GF1 MIDI interface - like UART 6850
 *
 *
 *   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 "gus.h"

static void snd_gf1_interrupt_midi_in( snd_gus_card_t *gus )
{
  int count;
  unsigned char stat, data, byte;
  snd_rawmidi_t *rmidi;

  rmidi = gus -> midi_uart;
  count = 10;
  while ( count ) {
    stat = snd_gf1_uart_stat( gus );
    if ( !(stat & 0x01) ) {		/* data in Rx FIFO? */
      count--;
      continue;
    }
    count = 100;			/* arm counter to new value */
    data = snd_gf1_uart_get( gus );
#if 0
    snd_printk( "midi in!!! data = 0x%x, stat = 0x%x\n", data, stat );
#endif
    if ( stat & 0x10 ) {		/* framing error */
      gus -> gf1.uart_framing++;
      if ( rmidi -> input.reset )
        rmidi -> input.reset( rmidi );
      continue;
    }
    byte = snd_gf1_uart_get( gus );
    rmidi -> input.data( rmidi, &byte, 1 );
    if ( stat & 0x20 ) {
      gus -> gf1.uart_overrun++;
      if ( rmidi -> input.reset )
        rmidi -> input.reset( rmidi );
    }
  }
}

static void snd_gf1_interrupt_midi_out( snd_gus_card_t *gus )
{
  char byte;
  snd_rawmidi_t *rmidi;

  rmidi = gus -> midi_uart;

  /* try unlock output */
  if ( snd_gf1_uart_stat( gus ) & 0x01 )
    snd_gf1_interrupt_midi_in( gus );

  if ( snd_gf1_uart_stat( gus ) & 0x02 ) {			/* Tx FIFO free? */
    if ( rmidi -> output.data( rmidi, &byte, 1 ) != 1 ) {	/* no other bytes or error */
      /* high layer already trigger down output */
      return;
    }
#if 0
    snd_printk( "IRQ tx = 0x%x\n", byte );
#endif
    snd_gf1_uart_put( gus, byte );
  }
}

static void snd_gf1_uart_reset( snd_gus_card_t *gus, int close )
{
  snd_gf1_uart_cmd( gus, 0x03 );		/* reset */
  if ( !close && gus -> uart_enable ) {
    snd_delay( 16 );
    snd_gf1_uart_cmd( gus, 0x00 );		/* normal operations */
  }
}

static int snd_gf1_uart_output_open( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  snd_gus_card_t *gus;
  
  gus = (snd_gus_card_t *)rmidi -> private_data;
  snd_spin_lock( gus, uart_cmd, &flags );
  if ( !(gus -> gf1.uart_cmd & 0x80) ) {	/* input active? */    
    snd_gf1_uart_reset( gus, 0 );
  }
  gus -> gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out;
  snd_spin_unlock( gus, uart_cmd, &flags );
#if 0
  snd_printk( "write init - cmd = 0x%x, stat = 0x%x\n", gus -> gf1.uart_cmd, snd_gf1_uart_stat( gus ) );
#endif
  return 0;
}

static int snd_gf1_uart_input_open( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  snd_gus_card_t *gus;
  int i;

  gus = (snd_gus_card_t *)rmidi -> private_data;
  snd_spin_lock( gus, uart_cmd, &flags );
  if ( gus -> gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out ) {
    snd_gf1_uart_reset( gus, 0 );
  }
  gus -> gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in;
  if ( gus -> uart_enable ) {
    for ( i = 0; i < 1000 && ( snd_gf1_uart_stat( gus ) & 0x01 ); i++ )
      snd_gf1_uart_get( gus );					/* clean Rx */
    if ( i >= 1000 )
      snd_printk( "gus midi uart init read - cleanup error\n" );
  }
  snd_spin_unlock( gus, uart_cmd, &flags );
#if 0
  snd_printk( "read init - enable = %i, cmd = 0x%x, stat = 0x%x\n", gus -> uart_enable, gus -> gf1.uart_cmd, snd_gf1_uart_stat( gus ) );
  snd_printk( "[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x (page = 0x%x)\n", gus -> gf1.port + 0x100, inb( gus -> gf1.port + 0x100 ), inb( gus -> gf1.port + 0x101 ), inb( gus -> gf1.port + 0x102 ) );
#endif
  return 0;
}

static int snd_gf1_uart_output_close( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)rmidi -> private_data;
  snd_spin_lock( gus, uart_cmd, &flags );  
  if ( gus -> gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in )
    snd_gf1_uart_reset( gus, 1 );
  snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_MIDI_OUT );
  snd_spin_unlock( gus, uart_cmd, &flags );
  return 0;  
}

static int snd_gf1_uart_input_close( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  snd_gus_card_t *gus;

  gus = (snd_gus_card_t *)rmidi -> private_data;
  snd_spin_lock( gus, uart_cmd, &flags );
  if ( gus -> gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out )
    snd_gf1_uart_reset( gus, 1 );
  snd_gf1_set_default_handlers( gus, SND_GF1_HANDLER_MIDI_IN );
  snd_spin_unlock( gus, uart_cmd, &flags );
  return 0;
}

static void snd_gf1_uart_input_trigger( snd_rawmidi_t *rmidi, int up )
{
  snd_gus_card_t *gus;
  unsigned long flags;
  
  gus = (snd_gus_card_t *)rmidi -> private_data;

  snd_spin_lock( gus, uart_cmd, &flags );
  if ( up )
    snd_gf1_uart_cmd( gus, gus -> gf1.uart_cmd | 0x80 );	/* enable Rx interrupts */
   else
    snd_gf1_uart_cmd( gus, gus -> gf1.uart_cmd & ~0x80 );	/* disable Rx interrupts */    
  snd_spin_unlock( gus, uart_cmd, &flags );
#if 0
  snd_printk( "midi input trigger - cmd = 0x%x\n", gus -> gf1.uart_cmd );
#endif
}

static void snd_gf1_uart_output_trigger( snd_rawmidi_t *rmidi, int up )
{
  unsigned long flags;
  snd_gus_card_t *gus;
  char byte;
  int timeout, result;

  gus = (snd_gus_card_t *)rmidi -> private_data;

  if ( up ) {
    /* for for empty Rx - Tx is probably unlocked */
    timeout = 10000;
    while ( timeout-- > 0 && snd_gf1_uart_stat( gus ) & 0x01 ) ;

    /* Tx FIFO free? */
    result = 0;
    snd_spin_lock( gus, uart_cmd, &flags );
    if ( gus -> gf1.uart_cmd & 0x20 ) {
      snd_spin_unlock( gus, uart_cmd, &flags );
      return;
    }
    if ( snd_gf1_uart_stat( gus ) & 0x02 ) {
      if ( rmidi -> output.data( rmidi, &byte, 1 ) != 1 ) {
        snd_spin_lock( gus, uart_cmd, &flags );
        return;
      }
#if 0
      snd_printk( "tx = 0x%x\n", byte );
#endif
      snd_gf1_uart_put( gus, byte );
      result = 1;
    }
    snd_gf1_uart_cmd( gus, gus -> gf1.uart_cmd | 0x20 );	/* enable Tx interrupt */
    snd_spin_unlock( gus, uart_cmd, &flags );
  } else {
    snd_spin_lock( gus, uart_cmd, &flags );
    snd_gf1_uart_cmd( gus, gus -> gf1.uart_cmd & ~0x20 );
    snd_spin_unlock( gus, uart_cmd, &flags );
  }
}

static struct snd_stru_rawmidi_direction_hw snd_gf1_uart_output = {
  0,				/* flags */
  NULL,				/* private_data */
  NULL,				/* private_free */
  snd_gf1_uart_output_open,	/* open */
  snd_gf1_uart_output_close,	/* close */
  snd_gf1_uart_output_trigger,	/* trigger */
  { NULL },			/* io.write */
  NULL,				/* abort */
};

static struct snd_stru_rawmidi_direction_hw snd_gf1_uart_input = {
  0,				/* flags */
  NULL,				/* private_data */
  NULL,				/* private_free */
  snd_gf1_uart_input_open,	/* open */
  snd_gf1_uart_input_close,	/* close */
  snd_gf1_uart_input_trigger,	/* trigger */
  { NULL },			/* io.read */
  NULL,				/* abort */
};

snd_rawmidi_t *snd_gf1_rawmidi_new_device( snd_gus_card_t *gus )
{
  snd_rawmidi_t *rmidi;
  
  if ( ( rmidi = snd_rawmidi_new_device( gus -> card, "GF1" ) ) == NULL ) return NULL;
  strcpy( rmidi -> name, gus -> interwave ? "AMD InterWave" : "GF1" );
  memcpy( &rmidi -> output.hw, &snd_gf1_uart_output, sizeof( snd_gf1_uart_output ) );
  memcpy( &rmidi -> input.hw, &snd_gf1_uart_input, sizeof( snd_gf1_uart_input ) );
  rmidi -> info_flags |= SND_RAWMIDI_INFO_OUTPUT | SND_RAWMIDI_INFO_INPUT | SND_RAWMIDI_INFO_DUPLEX;
  rmidi -> private_data = gus;
  gus -> midi_uart = rmidi;
  return rmidi;
}
