/*
 * sound/uart401.c
 *
 * MPU-401 UART driver (formerly uart401_midi.c)
 */
/*
 * Copyright (C) by Hannu Savolainen 1993-1996
 *
 * USS/Lite for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
 * Version 2 (June 1991). See the "COPYING" file distributed with this software
 * for more info.
 */
#include <linux/config.h>


#include "sound_config.h"

#if defined(CONFIG_UART401) && defined(CONFIG_MIDI)

typedef struct uart401_devc
  {
    int             base;
    int             irq;
    int            *osp;
    void            (*midi_input_intr) (int dev, unsigned char data);
    int             opened;
    volatile unsigned char input_byte;
    int             my_dev;
    int             share_irq;
  }
uart401_devc;

static uart401_devc *detected_devc = NULL;
static uart401_devc *irq2devc[16] =
{NULL};

#define	DATAPORT   (devc->base)
#define	COMDPORT   (devc->base+1)
#define	STATPORT   (devc->base+1)

static int 
uart401_status (uart401_devc * devc)
{
  return inb (STATPORT);
}
#define input_avail(devc) (!(uart401_status(devc)&INPUT_AVAIL))
#define output_ready(devc)	(!(uart401_status(devc)&OUTPUT_READY))
static void 
uart401_cmd (uart401_devc * devc, unsigned char cmd)
{
  outb (cmd, COMDPORT);
}
static int 
uart401_read (uart401_devc * devc)
{
  return inb (DATAPORT);
}
static void 
uart401_write (uart401_devc * devc, unsigned char byte)
{
  outb (byte, DATAPORT);
}

#define	OUTPUT_READY	0x40
#define	INPUT_AVAIL	0x80
#define	MPU_ACK		0xFE
#define	MPU_RESET	0xFF
#define	UART_MODE_ON	0x3F

static int      reset_uart401 (uart401_devc * devc);

static void
uart401_input_loop (uart401_devc * devc)
{
  while (input_avail (devc))
    {
      unsigned char   c = uart401_read (devc);

      if (c == MPU_ACK)
	devc->input_byte = c;
      else if (devc->opened & OPEN_READ && devc->midi_input_intr)
	devc->midi_input_intr (devc->my_dev, c);
    }
}

void
uart401intr (int irq, void *dev_id, struct pt_regs *dummy)
{
  uart401_devc   *devc;

  if (irq < 1 || irq > 15)
    return;

  devc = irq2devc[irq];

  if (devc == NULL)
    return;

  if (input_avail (devc))
    uart401_input_loop (devc);
}

static int
uart401_open (int dev, int mode,
	      void            (*input) (int dev, unsigned char data),
	      void            (*output) (int dev)
)
{
  uart401_devc   *devc = (uart401_devc *) midi_devs[dev]->devc;

  if (devc->opened)
    {
      return -(EBUSY);
    }

  while (input_avail (devc))
    uart401_read (devc);

  devc->midi_input_intr = input;
  devc->opened = mode;

  return 0;
}

static void
uart401_close (int dev)
{
  uart401_devc   *devc = (uart401_devc *) midi_devs[dev]->devc;

  devc->opened = 0;
}

static int
uart401_out (int dev, unsigned char midi_byte)
{
  int             timeout;
  unsigned long   flags;
  uart401_devc   *devc = (uart401_devc *) midi_devs[dev]->devc;

  /*
   * Test for input since pending input seems to block the output.
   */

  save_flags (flags);
  cli ();

  if (input_avail (devc))
    uart401_input_loop (devc);

  restore_flags (flags);

  /*
   * Sometimes it takes about 13000 loops before the output becomes ready
   * (After reset). Normally it takes just about 10 loops.
   */

  for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--);

  if (!output_ready (devc))
    {
      printk ("MPU-401: Timeout\n");
      return 0;
    }

  uart401_write (devc, midi_byte);
  return 1;
}

static int
uart401_start_read (int dev)
{
  return 0;
}

static int
uart401_end_read (int dev)
{
  return 0;
}

static int
uart401_ioctl (int dev, unsigned cmd, caddr_t arg)
{
  return -(EINVAL);
}

static void
uart401_kick (int dev)
{
}

static int
uart401_buffer_status (int dev)
{
  return 0;
}

#define MIDI_SYNTH_NAME	"MPU-401 UART"
#define MIDI_SYNTH_CAPS	SYNTH_CAP_INPUT
#include "midi_synth.h"

static struct midi_operations uart401_operations =
{
  {"MPU-401 (UART) MIDI", 0, 0, SNDCARD_MPU401},
  &std_midi_synth,
  {0},
  uart401_open,
  uart401_close,
  uart401_ioctl,
  uart401_out,
  uart401_start_read,
  uart401_end_read,
  uart401_kick,
  NULL,
  uart401_buffer_status,
  NULL
};

static void
enter_uart_mode (uart401_devc * devc)
{
  int             ok, timeout;
  unsigned long   flags;

  save_flags (flags);
  cli ();
  for (timeout = 30000; timeout < 0 && !output_ready (devc); timeout--);

  devc->input_byte = 0;
  uart401_cmd (devc, UART_MODE_ON);

  ok = 0;
  for (timeout = 50000; timeout > 0 && !ok; timeout--)
    if (devc->input_byte == MPU_ACK)
      ok = 1;
    else if (input_avail (devc))
      if (uart401_read (devc) == MPU_ACK)
	ok = 1;

  restore_flags (flags);
}

void
attach_uart401 (struct address_info *hw_config)
{
  uart401_devc   *devc;
  char           *name = "MPU-401 (UART) MIDI";

  if (hw_config->name)
    name = hw_config->name;

  if (detected_devc == NULL)
    return;


  devc = (uart401_devc *) (sound_mem_blocks[sound_nblocks] = vmalloc (sizeof (uart401_devc)));
  if (sound_nblocks < 1024)
    sound_nblocks++;;
  if (devc == NULL)
    {
      printk ("uart401: Can't allocate memory\n");
      return;
    }

  memcpy ((char *) devc, (char *) detected_devc, sizeof (uart401_devc));
  detected_devc = NULL;

  devc->irq = hw_config->irq;
  if (devc->irq < 0)
    {
      devc->share_irq = 1;
      devc->irq *= -1;
    }
  else
    devc->share_irq = 0;

  if (devc->irq < 1 || devc->irq > 15)
    return;

  if (!devc->share_irq)
    if (snd_set_irq_handler (devc->irq, uart401intr, "uart401", devc->osp) < 0)
      {
	printk ("uart401: Failed to allocate IRQ%d\n", devc->irq);
	return;
      }

  irq2devc[devc->irq] = devc;
  devc->my_dev = num_midis;

  request_region (hw_config->io_base, 4, "SB MIDI");
  enter_uart_mode (devc);

  if (num_midis >= MAX_MIDI_DEV)
    {
      printk ("Sound: Too many midi devices detected\n");
      return;
    }

  conf_printf (name, hw_config);

  std_midi_synth.midi_dev = devc->my_dev = num_midis;


  midi_devs[num_midis] = (struct midi_operations *) (sound_mem_blocks[sound_nblocks] = vmalloc (sizeof (struct midi_operations)));

  if (sound_nblocks < 1024)
    sound_nblocks++;;
  if (midi_devs[num_midis] == NULL)
    {
      printk ("uart401: Failed to allocate memory\n");
      return;
    }

  memcpy ((char *) midi_devs[num_midis], (char *) &uart401_operations,
	  sizeof (struct midi_operations));

  midi_devs[num_midis]->devc = devc;


  midi_devs[num_midis]->converter = (struct synth_operations *) (sound_mem_blocks[sound_nblocks] = vmalloc (sizeof (struct synth_operations)));

  if (sound_nblocks < 1024)
    sound_nblocks++;;

  if (midi_devs[num_midis]->converter == NULL)
    {
      printk ("uart401: Failed to allocate memory\n");
      return;
    }

  memcpy ((char *) midi_devs[num_midis]->converter, (char *) &std_midi_synth,
	  sizeof (struct synth_operations));

  strcpy (midi_devs[num_midis]->info.name, name);
  num_midis++;
  devc->opened = 0;
}

static int
reset_uart401 (uart401_devc * devc)
{
  int             ok, timeout, n;

  /*
   * Send the RESET command. Try again if no success at the first time.
   */

  ok = 0;

  /* save_flags(flags);cli(); */

  for (n = 0; n < 2 && !ok; n++)
    {
      for (timeout = 30000; timeout < 0 && !output_ready (devc); timeout--);

      devc->input_byte = 0;
      uart401_cmd (devc, MPU_RESET);

      /*
       * Wait at least 25 msec. This method is not accurate so let's make the
       * loop bit longer. Cannot sleep since this is called during boot.
       */

      for (timeout = 50000; timeout > 0 && !ok; timeout--)
	if (devc->input_byte == MPU_ACK)	/* Interrupt */
	  ok = 1;
	else if (input_avail (devc))
	  if (uart401_read (devc) == MPU_ACK)
	    ok = 1;

    }

  if (ok)
    uart401_input_loop (devc);	/*
				 * Flush input before enabling interrupts
				 */

  /* restore_flags(flags); */

  return ok;
}

int
probe_uart401 (struct address_info *hw_config)
{
  int             ok = 0;

  static uart401_devc hw_info;
  uart401_devc   *devc = &hw_info;

  detected_devc = NULL;

  if (check_region (hw_config->io_base, 4))
    return 0;

  devc->base = hw_config->io_base;
  devc->irq = hw_config->irq;
  devc->osp = hw_config->osp;
  devc->midi_input_intr = NULL;
  devc->opened = 0;
  devc->input_byte = 0;
  devc->my_dev = 0;
  devc->share_irq = 0;

  ok = reset_uart401 (devc);

  if (ok)
    detected_devc = devc;

  return ok;
}

void
unload_uart401 (struct address_info *hw_config)
{
  uart401_devc   *devc;

  int             irq = hw_config->irq;

  if (irq < 0)
    irq *= -1;

  if (irq < 1 || irq > 15)
    return;

  devc = irq2devc[irq];
  if (devc == NULL)
    return;

  reset_uart401 (devc);

  release_region (hw_config->io_base, 4);

  if (!devc->share_irq)
    snd_release_irq (devc->irq);
}


#endif
