/*
**  jazz - a midi sequencer for Linux
**
**  Copyright (C) 1994-1996 Andreas Voss (andreas@avix.rhein-neckar.de)
**
**  Copyright (C) 1995-1996 Per Sigmond (Per.Sigmond@hia.no)
**
**  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.
*/

/*
 * Roland MPU-401 MIDI-Driver
 * 
 * Andreas Voss (andreas@avix.rhein-neckar.de)
 * Per Sigmond (Per.Sigmond@hia.no)
 *
 * mpu_write():
 *
 *  writes Data to MPU's Registers in MPU-Format with 1 additional leading Byte:
 *     Bits 0..5: length of MPU-Data following.
 *     Bits 6..7:
 *       00 : send later on Track Data Request Interrupt 
 *            (0xF0-0xF6, 7 Tracks supported, track number follows leading byte)
 *       01 : write immediately to Data Register
 *       10 : write immediately to Command Register
 *       11 : driver command: 0xc0 = reset everything
 *
 * mpu_read()
 *    returns recorded data in mpu-format
 */

#include <linux/config.h>
#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#endif

#include <fcntl.h>
#include <linux/major.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/signal.h>
#include <linux/errno.h>

#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <asm/irq.h>

#include "mpu.h"


/* from drv_hello.c (modules-example): */

/*
 * we should include the kernel idenfication string in the module.
 */

#ifdef OLD_KERNEL
static char kernel_version[] = UTS_RELEASE;
#endif

/* nice and high, but it is tunable: insmod drv_hello major=30 */
static int major = 13;

/* --------------------- the driver --------------------- */

struct mpu_queue
{
  int rd, wr;
  unsigned char buffer[MPU_BUFFER_SIZE];
};

struct mpu_status
{
  struct mpu_queue recvq;
  struct mpu_queue sendq[ACTIVE_TRACKS];
  int running_status;
  struct wait_queue *sleeper;
};

static struct mpu_status mpu;


/* get char from queue */
static inline int mpu_readq(struct mpu_queue *q)
{
  unsigned char c;
  if (q->wr == q->rd)
    return -1;
  c = q->buffer[q->rd];
  q->rd = (q->rd + 1) % MPU_BUFFER_SIZE;
  return c;
}

/* put char into queue */
static inline int mpu_writeq(struct mpu_queue *q, unsigned char c)
{
  q->buffer[q->wr] = c;
  q->wr = (q->wr + 1) % MPU_BUFFER_SIZE;
  return 0;
}

/* true, if queue not empty */
static inline int mpu_statq(struct mpu_queue *q)
{
  return q->rd != q->wr;
}


/* # bytes free in queue */
static inline int mpu_leftq(struct mpu_queue *q)
{
  int w = q->wr;
  if (q->wr < q->rd)
    w += MPU_BUFFER_SIZE;
  return MPU_BUFFER_SIZE-1 - (w - q->rd);
}


static inline void mpu_clearq(struct mpu_queue *q)
{
  q->rd = q->wr = 0;
}

/*
 * MPU-Hardware
 */

#define mpu_rdd()  inb(MPU_IOBASE)
#define mpu_wrd(a) outb(a, MPU_IOBASE)
#define mpu_cmd(a) outb(a, MPU_IOBASE+1)

#define MPU_DSR 0x80
#define MPU_DRR 0x40
#define MPU_ACK 0xfe



static inline unsigned char mpu_status(void)
{
  return inb(MPU_IOBASE+1);
}

static inline int wait_drr(void)
{
  int try = 0;
  while ((mpu_status() & MPU_DRR) && try++ < MPU_WAIT)
    ;
  return try < MPU_WAIT ? 0 : -ENODEV;
}

static inline int wait_dsr(void)
{
  int try = 0;
  while ((mpu_status() & MPU_DSR) && try++ < MPU_WAIT)
    ;
  return try < MPU_WAIT ? 0 : -ENODEV;
}


static inline int mpu_write(char a)
{
  if (wait_drr() < 0)
    return -ENODEV;
  mpu_wrd(a);
  return 0;
}

static inline int mpu_read(void)
{
  if (wait_dsr() < 0)
    return -ENODEV;
  return (unsigned char)mpu_rdd();
}


static inline int mpu_reset(void)
{
  (void)wait_drr();
  mpu_cmd(0xff);
  (void)mpu_read();

  (void)wait_drr();
  mpu_cmd(0xff);
  (void)mpu_read();

  return 0;	/* always ok */
}


static inline int mpu_received(int c)
{
  int n;
  int track;

  /* length of midi-event             8  9  A  B  C  D  E */
  static const int three_bytes[8] = { 1, 1, 1, 1, 0, 0, 1 };

  if (c < 0xf0)	
  {
    /*
     * found timing byte. store it and get midi-message or mpu-mark
     */

    mpu_writeq(&mpu.recvq, c);	/* save timing byte to record-buffer */
    c = mpu_read();		/* get statusbyte (or 1st data, if running status) */
    mpu_writeq(&mpu.recvq, c);	/* store it, so we know what happend at this time */

    if (c < 0xf0)
    {
      /*
       * it's a midi-voice-message
       */

      if (c & 0x80)			/* new running status */
      {
	mpu.running_status = c;
	c = mpu_read();			/* get 1st data-byte */
	mpu_writeq(&mpu.recvq, c);
      }
      if (three_bytes[((unsigned char)mpu.running_status >> 4) - 8])
      {
	c = mpu_read();
	mpu_writeq(&mpu.recvq, c);
      }
    }

    /* 
     * else: mpu mark's are already stored
     */

  }

  else if (c < 0xf8)	
  {

    /* 
     * play track data request 
     */
    track = c - 0xf0;
    if (track >= ACTIVE_TRACKS) return( -EINVAL ); /* Highest track is reserved */
						   /* for immediate data */

    if (!mpu_statq(&mpu.sendq[track]))	/* nothing to send?? */
    {
      mpu_write(0xf8);		/* wait 240 ticks */
      return -EINVAL;		/* stupid since called by interrupt */
    }
    n = mpu_readq(&mpu.sendq[track]);
    while (n-- > 0)
    {
      c = mpu_readq(&mpu.sendq[track]);
      mpu_write(c);
    }

    /*
     * someone waiting for sendq empty?
     */

#if 0
    if (mpu.sleeper && mpu_leftq(&mpu.sendq[track]) > MPU_BUFFER_SIZE/2)
    {
      wake_up_interruptible(&mpu.sleeper);
      mpu.sleeper = NULL;
    }
#endif
  } 


  else
  {
    /* 
     * got mpu-stuff
     */

    switch (c)
    {
      case 0xfe:	/* ignore ack's */
        break;

      case 0xff:	/* system message */
	c = mpu_read();	/* get message type */
	mpu_writeq(&mpu.recvq, c); /* Write it */

	switch (c) /* What kind of message */
	{
		case 0xf0: /* sysex */
			while (c != 0xf7)
			{
				c = mpu_read();	/* get sysex data */
				mpu_writeq(&mpu.recvq, c);
			}
			break;
		case 0xf1: /* MTC quarter frame */
			c = mpu_read();	/* get quarter frame data */
			mpu_writeq(&mpu.recvq, c);
			break;
		case 0xf2: /* Song pointer */
			c = mpu_read();	/* get 1'st songpointer data */
			mpu_writeq(&mpu.recvq, c);
			c = mpu_read();	/* get 2'nd songpointer data */
			mpu_writeq(&mpu.recvq, c);
			break;
		case 0xf3: /* Song select */
			c = mpu_read();	/* get songselect data */
			mpu_writeq(&mpu.recvq, c);
			break;
		default:
			/* No more bytes on the others */
	}
	break;
	
      /*
       * implemented:
       *   f8: timer overflow
       *   fc: end of data
       *   fd: clock to host
       *
       * not implemented:
       *   all others (real time messages, conductor etc). record them
       *   so the application might discover an error.
       */

      default:
	mpu_writeq(&mpu.recvq, c);
	break;
    }
  }
  return 0;
}



static int mpu_command(unsigned char cmd)
{
  int try = 0;
  unsigned char c;
  unsigned long flags;

  if (wait_drr() < 0)
    return -ENODEV;

  /*
   * i'm not shure about disabling intr's, but mpu-manual says so
   */

  save_flags(flags);
  cli();

  mpu_cmd(cmd);

  while (try++ < MPU_WAIT)
  {
    if (wait_dsr() < 0)
    {
      try = MPU_WAIT;
      break;
    }

    c = mpu_rdd();

    if (c != MPU_ACK)
      mpu_received(c);
    else
      break;
  }

  restore_flags(flags);

  return try < MPU_WAIT ? 0 : -ENODEV;
}


static void mpu_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
  mpu_received(mpu_read());
  /* we may or may not have received something */
  if (mpu_statq(&mpu.recvq))
    wake_up_interruptible(&mpu.sleeper);
}


static void release_mpu(struct inode * inode, struct file * file)
{
  mpu_reset();
  free_irq(MPU_IRQ, NULL);

  MOD_DEC_USE_COUNT;
}

static int open_mpu(struct inode * inode, struct file * file)
{
int i;

  if (request_irq(MPU_IRQ, mpu_interrupt, 0, "mpu-401", NULL))
    return -EBUSY;

  if (mpu_reset() < 0)
  {
    free_irq(MPU_IRQ, NULL);
    return -ENODEV;
  }

  mpu_clearq(&mpu.recvq);
  for (i = 0; i < ACTIVE_TRACKS; i++ ) mpu_clearq(&mpu.sendq[i]);
  mpu.running_status = 0x90;
  mpu.sleeper = 0;

  MOD_INC_USE_COUNT;

  return 0;
}


#ifdef OLD_KERNEL
static int write_mpu(struct inode * inode, struct file * file, char * buffer, int count)
#else
static int write_mpu(struct inode * inode, struct file * file, const char * buffer, int count)
#endif
{
  char c;
  const char *temp;
  int err, n;
  int track;

  temp = buffer;

  while (count > 0)
  {
    c = get_fs_byte(temp++);
    count--;
    n = c & 0x3f;
    if (n > count)
    {
	/* got an incomlete event, return amount processed so far */
	return temp - buffer - 1;
    }

    switch ((unsigned char)c >> 6)
    {

      case 0: /* write into send-queue */

	/* Get track number */
	c = get_fs_byte(temp++);
	count--;
	track = c;
	if ( (track < 0) || (track >= ACTIVE_TRACKS) ) return( -EINVAL );

        while (n + 1 > mpu_leftq(&mpu.sendq[track]))	
        {
          if (file->f_flags & O_NONBLOCK)
	    return temp - buffer - 2;	/* resend command-byte */
					/* and track-number-byte */
	  interruptible_sleep_on(&mpu.sleeper);
        }

	n--; /* Skip track number byte */
	mpu_writeq(&mpu.sendq[track], n);
	while (n-- > 0)
	{
	  c = get_fs_byte(temp++);
	  count--;
	  mpu_writeq(&mpu.sendq[track], c);
	}
	break;


      case 1: /* write to data-register */ 

	while (n-- > 0)
	{
	  c = get_fs_byte(temp++);
	  count--;
	  if ((err = mpu_write(c)) < 0)
	    return err;
	}
	break;


      case 2: /* write to command-register */

	while (n-- > 0)
	{
	  c = get_fs_byte(temp++);
	  count--;
	  if ((err = mpu_command(c)) < 0)
	    return err;
	}
	break;


      default:	/* reset everything */
      {
	unsigned long flags;
	int i;
	mpu_reset();

	save_flags(flags);
	cli();
	mpu_clearq(&mpu.recvq);
	for (i = 0; i < ACTIVE_TRACKS; i++ ) mpu_clearq(&mpu.sendq[i]);
	mpu.running_status = 0x90;
	restore_flags(flags);
      }
      break;

    }
  }
  return temp - buffer;
}


static int read_mpu(struct inode * inode, struct file * file, char * buffer, int count)
{
  char c, *temp = buffer;
  while (count > 0 && mpu_statq(&mpu.recvq))
  {
    c = mpu_readq(&mpu.recvq);
    put_fs_byte(c, temp++);
    count --;
  }
  return temp - buffer;
}

static int select_mpu( struct inode *inode, struct file *file, int sel_type, select_table * wait )
{
	if (sel_type != SEL_IN) return(0);
	if (mpu_statq(&mpu.recvq)) return(1);
	select_wait( &mpu.sleeper, wait );
	return(0);
}

struct file_operations mpu_fops = {
	NULL,		/* mpu_seek */
	read_mpu,
	write_mpu,
	NULL, 		/* mpu_readdir */
	select_mpu,	/* mpu_select */
	NULL, 		/* mpu_ioctl */
	NULL,		/* mpu_mmap */
	open_mpu,
	release_mpu,
};


#ifdef MODULE

/*
 * And now the modules code and kernel interface.
 */

#ifdef __cplusplus
extern "C" {
#endif

extern int printk(const char* fmt, ...);

int
init_module( void)
{
  if (register_chrdev(major, "mpu401", &mpu_fops))
  {
    printk("unable to get major %d for mpu device\n", major);
    return -EIO;
  }
  return 0;
}

void
cleanup_module(void)
{
  if (unregister_chrdev(major, "mpu401") != 0)
    printk("cleanup_module failed\n");
}

#ifdef __cplusplus
}
#endif

#else /* MODULE */

unsigned long mpu_init(unsigned long kmem_start)
{
  printk("MPU-Driver here (av/ps, version 0.91-mt midinetd).\n");
  if (register_chrdev(13, "mpu", &mpu_fops))
    printk("unable to get major 13 for mpu device\n");
  return kmem_start;
}

#endif /* MODULE */

