/* linux/kernel/chr_drv/sound/sequencer.c

The sequencer personality manager.

(C) 1992  Hannu Savolainen (hsavolai@cs.helsinki.fi) */

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/tty.h>
#include <linux/ctype.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <sys/kd.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/soundcard.h>
#include "sound_calls.h"
#include "sound_config.h"

#ifdef CONFIGURE_SOUNDCARD

#ifndef EXCLUDE_SEQUENCER

#include "dev_table.h"

#define OUTB outb
#define DEB(WHAT)		/* (WHAT) */
#define DEB1(WHAT)		/* (WHAT) */

static int sequencer_ok = 0;

static struct wait_queue *seq_sleeper = NULL;
static struct wait_queue *midi_sleeper = NULL;

static int midi_opened = 0;	/* 1 if the process has opened MIDI */
static int midi_written = 0;

static long seq_time = 0;	/* Reference point for the timer */

static unsigned char queue[SEQ_MAX_QUEUE][4];
static unsigned char iqueue[SEQ_MAX_QUEUE][4];
static volatile int qhead = 0, qtail = 0, qlen = 0;
static volatile int iqhead = 0, iqtail = 0, iqlen = 0;
static volatile int seq_playing = 0;
static int sequencer_busy = 0;

static int seq_queue (unsigned char *note);
static void seq_startplay (void);
static int seq_sync (void);
static void seq_reset (void);
static void request_timer (int count);

int
sequencer_read (struct inode *inode, struct file *file, char *buf, int count)
{
  int c = count, p = 0;

  while (c > 3)
    {
      if (!iqlen)
	{
	  interruptible_sleep_on (&midi_sleeper);
	  midi_sleeper = NULL;
	  if (!iqlen)
	    return count - c;
	}

      memcpy_tofs (&buf[p], &iqueue[iqhead][0], 4);
      p += 4;
      c -= 4;

      iqhead = (iqhead + 1) % SEQ_MAX_QUEUE;
      iqlen--;
    }

  return count - c;
}

void
sequencer_midi_input (unsigned char data)
{
  int tstamp;

  if (data == 0xfe)	/* Active sensing */
     return;		/* Ignore */

  if (iqlen >= (SEQ_MAX_QUEUE - 1))
  {
    if (midi_sleeper)
       wake_up (&midi_sleeper);
    return;			/* Overflow */
  }

  if (data & 0x80)		/* Status byte */
    {				/* Put a time stamp before the actual data
				   byte */
      tstamp = jiffies - seq_time;	/* Time since open() */
      tstamp = (tstamp << 8) | SEQ_WAIT;
      memcpy (iqueue[iqtail], (char *) &tstamp, 4);
      iqlen++;
      iqtail = (iqtail + 1) % SEQ_MAX_QUEUE;
    }

  iqueue[iqtail][0] = SEQ_MIDIPUTC;
  iqueue[iqtail][1] = data;
  iqueue[iqtail][2] = 0;
  iqueue[iqtail][3] = 0;

  iqlen++;
  iqtail = (iqtail + 1) % SEQ_MAX_QUEUE;

  if (midi_sleeper)
  {
    wake_up (&midi_sleeper);
  }
}

int
sequencer_write (struct inode *inode, struct file *file, char *buf, int count)
{
  int dev;
  unsigned char note[4];
  int p = 0, c;
  int err;


  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;

  DEB (printk ("sequencer_write(dev=%d, count=%d)\n", dev, count));

  c = count;

  while (c >= 4)
    {
      memcpy_fromfs (note, &buf[p], 4);

      if (note[0] == SEQ_MIDIPUTC)
	if (!midi_opened)
	  {
	    int mode;

	    if (!num_midis)
	      {
		printk ("Sequencer Error: No MIDI hardware installed\n");
		return -ENODEV;
	      }

	    mode = file->f_flags & O_ACCMODE;
	    if ((err = midi_devs[0]->open (0, mode)) < 0)
	      {
		seq_reset ();
		return err;
	      }

	    midi_opened = 1;
	  }

      if (!seq_queue (note))
	return count - c;
      p += 4;
      c -= 4;
    }

  return count;
}

static int
seq_queue (unsigned char *note)
{
  int nextq;

  /* Test if there is space in the queue */

  if ((nextq = ((qtail + 1) % SEQ_MAX_QUEUE)) == qhead)
    if (!seq_playing)
      seq_startplay ();		/* Give chance to drain the queue */

  if (((nextq = ((qtail + 1) % SEQ_MAX_QUEUE)) == qhead) && !seq_sleeper)
    {
      interruptible_sleep_on (&seq_sleeper);	/* Sleep until the queue is
						   almost empty */
    }

  if ((nextq = ((qtail + 1) % SEQ_MAX_QUEUE)) == qhead)
    return 0;			/* To be sure */

  qlen++;

  memcpy (&queue[qtail][0], note, 4);
  qtail = nextq;

  if (!seq_playing)
    seq_startplay ();

  return 1;
}

static void
seq_startplay (void)
{
  int This;
  unsigned long *delay;
  unsigned char *q;

  while (qhead != qtail)
    {
      qhead = ((This = qhead) + 1) % SEQ_MAX_QUEUE;
      qlen--;

      q = &queue[This][0];

      switch (q[0])
	{
	case SEQ_FMNOTEOFF:
	  synth_devs[0]->kill_note (0, q[1]);
	  break;

	case SEQ_FMNOTEON:
	  synth_devs[0]->start_note (0, q[1], q[2], q[3]);
	  break;

	case SEQ_DRUMOFF:
	  synth_devs[0]->kill_drum (0, q[2]);
	  break;

	case SEQ_DRUMON:
	  synth_devs[0]->start_drum (0, q[1], q[2], q[3]);
	  break;

	case SEQ_WAIT:
	  delay = (unsigned long *) q;	/* Bytes 1 to 3 are containing the
					   delay in jiffies */
	  *delay = *delay >> 8;

	  if ((seq_time + *delay - jiffies < 10000) &&
	      (seq_time + *delay - jiffies > 0))
	    {
	      long time;

	      seq_playing = 1;
	      time = seq_time + *delay;	/* Times are relative to seq_time	 */
	      request_timer (time);
	      return;		/* Stop here. Timer routine will continue
				   playing after the delay */
	    }
	  break;

	case SEQ_FMPGMCHANGE:

	  synth_devs[0]->set_instr (0, q[1], q[2]);
	  break;

	case SEQ_SYNCTIMER:	/* Reset timer */
	  seq_time = jiffies;
	  break;

	case SEQ_MIDIPUTC:	/* Put a midi character */
	  if (midi_opened)
	    midi_written = 1;
	  if (!midi_devs[0]->putc (0, q[1]))
	    {
	      /* Output FIFO is full. Wait one clock cycle and try again. */

	      qlen++;
	      qhead = This;	/* Restore queue */
	      request_timer (jiffies + 1);
	      return;
	    };
	  break;

	default:;
	}

    }

  if (seq_sleeper)
    {
      struct wait_queue *tmpq;
      tmpq = seq_sleeper;
      seq_sleeper = NULL;
      wake_up (&tmpq);
    }

  seq_playing = 0;
}

int
sequencer_open (struct inode *inode, struct file *filp)
{
  int dev, retval, mode;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;
  mode = filp->f_flags & O_ACCMODE;

  DEB (printk ("sequencer_open(dev=%d) : usecount=%d\n", dev, sbc_devices[dev].usecount));

  if (!sequencer_ok)
    {
      printk ("Soundcard: Sequencer not initialized\n");
      return -ENODEV;
    }

  if (sequencer_busy)
    return -EBUSY;

  if (!(num_synths + num_midis))
    return -ENODEV;
  if (!synth_devs[0])
    return -ENODEV;

  if ((retval = synth_devs[0]->open (dev, mode)) < 0)
    return retval;

  seq_queue ("\004\000\000");	/* Syncronize timer */
  midi_written = 0;
  midi_opened = 0;

  if (mode == O_RDONLY || mode == O_RDWR)
    {				/* Initialize midi input */
      if (!num_midis)
	return -ENODEV;

      if ((retval = midi_devs[0]->open (0, mode)) < 0)
	return retval;
      midi_opened = 1;
    }
  sequencer_busy = 1;

  return 0;
}

void
sequencer_release (struct inode *inode, struct file *filp)
{
  int dev;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;

  DEB (printk ("sequencer_release(dev=%d)\n", dev));

  if (!(current->signal & ~current->blocked))
    {
      seq_sync ();
    }
  seq_reset ();

  if (midi_opened)
    midi_devs[0]->close (0);

  if (synth_devs[0])
    synth_devs[0]->close (dev);

  sequencer_busy = 0;
}

static int
seq_sync (void)
{
  if (qlen && !seq_sleeper)	/* Queue not empty */
    {
      interruptible_sleep_on (&seq_sleeper);	/* Sleep until the queue is
						   almost empty */
    }

  return qlen;
}

static void
midi_outc (int dev, unsigned char data)
{
  /* This routine sends one byte to the Midi channel. */
  /* If the output Fifo is full, it waits until there */
  /* is space in the queue */

  while (!midi_devs[dev]->putc (dev, data));
}

static void
seq_reset (void)
{
  int chn;

  timer_active &= ~(1 << SOUND_TIMER);	/* Deactivate timer */
  timer_table[SOUND_TIMER].expires = 0x7fffffff;

  if (synth_devs[0])
    synth_devs[0]->reset (0);

  if (midi_written)		/* Midi used. Some notes may still be playing */
    {
      for (chn = 0; chn < 16; chn++)
	{
	  midi_outc (0, 0xb0 + chn);	/* Channel message */
	  midi_outc (0, 0x7b);	/* All notes off */
	  midi_outc (0, 0);	/* Dummy parameter */
	}

      midi_devs[0]->close (0);
      midi_written = 0; 
      midi_opened = 0;
    }

  iqlen = iqhead = iqtail = 0;
  qlen = 0;
  qhead = 0;
  qtail = 0;
  midi_written = 0;
  seq_playing = 0;

  if (seq_sleeper)
    printk ("Sequencer Warning: Unexpected sleeping process\n");

}

int
sequencer_ioctl (struct inode *inode, struct file *file,
		 unsigned int cmd, unsigned int arg)
{
  int dev;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;

  switch (cmd)
    {

    case SNDCTL_SEQ_SYNC:
      return seq_sync ();
      break;

    case SNDCTL_SEQ_RESET:
      seq_reset ();
      return 0;
      break;

    case SNDCTL_SEQ_TESTMIDI:
      if (!midi_opened)
	{
	  int err, mode;

	  if (!num_midis)
	    return -ENODEV;

	  mode = file->f_flags & O_ACCMODE;
	  if ((err = midi_devs[0]->open (0, mode)) < 0)
	    return err;
	}

      midi_opened = 1;
      return 1;
      break;

    case SNDCTL_SEQ_GETINCOUNT:
      return iqlen;
      break;

    case SNDCTL_SEQ_GETOUTCOUNT:
      return SEQ_MAX_QUEUE-qlen;
      break;

    default:
      if (!synth_devs[0])
	return -EINVAL;
      return synth_devs[0]->ioctl (dev, cmd, arg);
      break;
    }

  return -EINVAL;
}

int sequencer_select(struct inode * inode, struct file * filp, int sel_type, select_table * wait)
{
  int dev;

  dev = inode->i_rdev;
  dev = MINOR (dev) >> 4;

	switch (sel_type) {
		case SEL_IN:
		      if (!iqlen)
			{
			  select_wait (&midi_sleeper, wait);
			  return 0;
			}

			return 1;
			break;

		case SEL_OUT:
			return 0;
		case SEL_EX:
			return 0;
	}

	return 0;
}

static void
sequencer_timer (void)
{
  seq_startplay ();
}

static void
request_timer (int count)
{

  timer_table[SOUND_TIMER].expires = count;
  timer_active |= 1 << SOUND_TIMER;
}

long
sequencer_init (long mem_start)
{
  timer_table[SOUND_TIMER].fn = sequencer_timer;
  timer_table[SOUND_TIMER].expires = 0;
  sequencer_ok = 1;

  return mem_start;
}
#else
/* Stub version */
int sequencer_read (struct inode *inode, struct file *file, char *buf, int count) {return -EIO;}
int sequencer_write (struct inode *inode, struct file *file, char *buf, int count) {return -EIO;}
int sequencer_open (struct inode *inode, struct file *filp) {return -ENODEV;}
void sequencer_release (struct inode *inode, struct file *filp) {return;}
int sequencer_ioctl (struct inode *inode, struct file *file,
	   unsigned int cmd, unsigned int arg) {return -EIO;}
int sequencer_lseek (struct inode *inode, struct file *file, off_t offset, int orig) {return -EIO;}
long sequencer_init (long mem_start) {return mem_start;}
void sequencer_midi_input(unsigned char data) {return;}
int sequencer_select(struct inode * inode, struct file * filp, int sel_type, select_table * wait) {return -EIO;}
#endif

#endif
