///////////////////////////////////////////////////////////////////////////
//      MOTU Midi Timepiece ALSA Main routines
//      Copyright by Michael T. Mayers (c) Jan 09, 2000
//      mail: tweakoz@pacbell.net
//      Thanks to John Galbraith
///////////////////////////////////////////////////////////////////////////
//      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.
///////////////////////////////////////////////////////////////////////////
//      This driver is for the 'Mark Of The Unicorn' (MOTU) MidiTimePiece AV multiport MIDI interface 
//
//      IOPORTS
//      ///////////
//      8 MIDI Ins and 8 MIDI outs
//      Video Sync In (BNC), Word Sync Out (BNC), 
//      ADAT Sync Out (DB9)
//      SMPTE in/out (1/4")
//      2 programmable pedal/footswitch inputs and 4 programmable MIDI controller knobs.
//      Macintosh RS422 serial port
//      RS422 "network" port for ganging multiple MTP's
//      PC Parallel Port ( which this driver currently uses )
//
//      MISC FEATURES
//      ///////////////
//      Hardware MIDI routing, merging, and filtering   
//      MIDI Synchronization to Video, ADAT, SMPTE and other Clock sources
//      128 'scene' memories, recallable from MIDI program change
///////////////////////////////////////////////////////////////////////////
//      remember to set SND_RAWMIDI_PORTS in rawmidi.h to >= 8 for 1 mtp or 16 for 2 mtps
///////////////////////////////////////////////////////////////////////////

#define SND_MAIN_OBJECT_FILE

/////////////////////////////////////////////////////////////////
//      includes

#include "../include/mtpav.h"

//////////////////////////////////////////////////////////////////

static void snd_mtpav_use_inc(snd_card_t * card);
static void snd_mtpav_use_dec(snd_card_t * card);
static void snd_mtpav_output_write(snd_rawmidi_t * rmidi);
static int snd_mtpav_input_open(snd_rawmidi_t * rmidi);
static int snd_mtpav_input_close(snd_rawmidi_t * rmidi);
static void snd_mtpav_input_trigger(snd_rawmidi_t * rmidi, int up);
static int snd_mtpav_output_open(snd_rawmidi_t * rmidi);
static int snd_mtpav_output_close(snd_rawmidi_t * rmidi);
static void snd_mtpav_output_trigger(snd_rawmidi_t * rmidi, int up);
static void snd_mtpav_mputreg(U16 reg, U8 val);

static void snd_mtpav_inmidi_h(TSmtp * mcrd, U8 inbyte);

/////////////////////////////////////////////////////////////////
//      globals

MODULE_DESCRIPTION("\
Driver: MOTU MidiTimePiece AV multiport MIDI\n\
");

int snd_index = SND_DEFAULT_IDX1;
char *snd_id = SND_DEFAULT_STR1;
int snd_port = 0x378;		/* 0x378, 0x278 */
int snd_irq = 7;		/* 7, 5 */
MODULE_PARM(snd_index, "i");
MODULE_PARM_DESC(snd_index, "Index value for MotuMTPAV MIDI.");
MODULE_PARM(snd_id, "s");
MODULE_PARM_DESC(snd_id, "ID string for MotuMTPAV MIDI.");
MODULE_PARM(snd_port, "i");
MODULE_PARM_DESC(snd_port, "Parallel port # for MotuMTPAV MIDI.");
MODULE_PARM(snd_irq, "i");
MODULE_PARM_DESC(snd_irq, "Parallel IRQ # for MotuMTPAV MIDI.");

static TSmtp *mtp_card;
//static U8 outmsg_lastport;
//static U8 inmsg_lastport;

static struct snd_stru_rawmidi_channel_hw snd_mtpav_output = {
	open:		snd_mtpav_output_open,
	close:		snd_mtpav_output_close,
	trigger:	snd_mtpav_output_trigger,
};

static struct snd_stru_rawmidi_channel_hw snd_mtpav_input = {
	open:		snd_mtpav_input_open,
	close:		snd_mtpav_input_close,
	trigger:	snd_mtpav_input_trigger,
};

///////////////////////////////////////////////////////////////////////////////

static U8 snd_mtpav_getreg(U16 reg)
{
	U8 rval = 0;

	if (reg == SREG) {
		rval = inb(MTPAV_IOBASE + SREG);
		rval = (rval & 0xf8);
	} else if (reg == CREG) {
		rval = inb(MTPAV_IOBASE + CREG);
		rval = (rval & 0x1c);
	}

	return rval;
}

///////////////////////////////////////////////////////////////////////////////

static void snd_mtpav_mputreg(U16 reg, U8 val)
{
	if (reg == DREG) {
		outb(val, (MTPAV_IOBASE + DREG));
	} else if (reg == CREG) {
		outb(val, (MTPAV_IOBASE + CREG));
	}
}

//////////////////////////////////////////////////////////////////////////

static void snd_mtpav_wait_rfdhi(void)
{
	U8 sbyte;

	sbyte = snd_mtpav_getreg(SREG);
	while (!(sbyte & SIGS_RFD)) {
		sbyte = snd_mtpav_getreg(SREG);
	}

}

static void snd_mtpav_send_byte(U8 byte)
{
	U8 tcbyt;
	U8 clrwrite;
	U8 setwrite;

	snd_mtpav_wait_rfdhi();

	/////////////////

	tcbyt = snd_mtpav_getreg(CREG);
	clrwrite = tcbyt & (SIGC_WRITE ^ 0xff);
	setwrite = tcbyt | SIGC_WRITE;

	snd_mtpav_mputreg(DREG, byte);
	snd_mtpav_mputreg(CREG, clrwrite);	// clear write bit

	snd_mtpav_mputreg(CREG, setwrite);	// set write bit

}

//

static void snd_mtpav_output_write(snd_rawmidi_t * rmidi)
{
	U8 outbyte;
	U8 ifsent;
	U32 port;
	unsigned long flags;


	/////////////////
	// send port change command if necessary

	port = (U32) rmidi->device;

	spin_lock_irqsave(&mtp_card->spinlock, flags);

	if (port != mtp_card->outmidiport) {
		mtp_card->outmidiport = port;

		snd_mtpav_send_byte(0xf5);
		snd_mtpav_send_byte(port);
		//snd_printk("new outport: 0x%x\n", (unsigned int) port);

	}
	///////////////////
	//      send data

	ifsent = 1;
	while (ifsent == 1) {
		ifsent = snd_rawmidi_transmit(rmidi, &outbyte, 1);

		if (ifsent == 1)
			snd_mtpav_send_byte(outbyte);
	}

	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
}

/////////////////////////////////////////////////////
//      mtpav control

static void snd_mtpav_portscan(void)	// put mtp into smart routing mode
{
	U8 port;

	for (port = 0; port < 8; port++) {
		snd_mtpav_send_byte(0xf5);
		snd_mtpav_send_byte(port);
		snd_mtpav_send_byte(0xfe);
	}
}

//////////////////////////////////////////////////////////////////////////

static int snd_mtpav_input_open(snd_rawmidi_t * rmidi)
{
	U32 flags;
	U32 port = (U32) rmidi->private_data;
	//printk("mtpav port: %d opened\n", (int) port);
	spin_lock_irqsave(&mtp_card->spinlock, flags);
	mtp_card->mode[port] |= MTPAV_MODE_INPUT_OPENED;
	snd_mtpav_mputreg(CREG, (SIGC_INTEN | SIGC_WRITE));	// enable pport interrupts

	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
	return 0;
}

//////////////////////////////////////////////////////////////////

static int snd_mtpav_input_close(snd_rawmidi_t * rmidi)
{
	U32 flags;
	U32 port = (U32) rmidi->private_data;
	UBOOL killirq = TRUE;
	U32 tport;

	//printk("mtpav port: %d closed\n", (int) port);

	spin_lock_irqsave(&mtp_card->spinlock, flags);

	mtp_card->mode[port] &= (~MTPAV_MODE_INPUT_OPENED);

	for (tport = 0; tport < NUMPORTS; tport++) {
		if (mtp_card->mode[tport] & MTPAV_MODE_INPUT_OPENED)
			killirq = FALSE;
	}

	if (killirq == TRUE)
		snd_mtpav_mputreg(CREG, 0);	// disable pport interrupts

	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
	return 0;
}

//////////////////////////////////////////////////////////////////

static void snd_mtpav_input_trigger(snd_rawmidi_t * rmidi, int up)
{
	U32 flags;
	U32 port = (U32) rmidi->private_data;

	spin_lock_irqsave(&mtp_card->spinlock, flags);
	if (up)
		mtp_card->mode[port] |= MTPAV_MODE_INPUT_TRIGGERED;
	else
		mtp_card->mode[port] &= ~MTPAV_MODE_INPUT_TRIGGERED;
	spin_unlock_irqrestore(&mtp_card->spinlock, flags);

}

///////////////////////////////////////////////////////////////////////////////

static int snd_mtpav_output_open(snd_rawmidi_t * rmidi)
{
	U32 flags;
	U32 port = (U32) rmidi->private_data;

	spin_lock_irqsave(&mtp_card->spinlock, flags);
	mtp_card->mode[port] |= MTPAV_MODE_OUTPUT_OPENED;
	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
	return 0;
};

///////////////////////////////////////////////////////////////////////////////

static int snd_mtpav_output_close(snd_rawmidi_t * rmidi)
{
	U32 flags;
	U32 port = (U32) rmidi->private_data;

	spin_lock_irqsave(&mtp_card->spinlock, flags);
	mtp_card->mode[port] &= (~MTPAV_MODE_OUTPUT_OPENED);
	spin_unlock_irqrestore(&mtp_card->spinlock, flags);
	return 0;
};

//////////////////////////////////////////////////////////////////

static void snd_mtpav_output_trigger(snd_rawmidi_t * rmidi, int up)
{
	U32 flags;
	U32 port = (U32) rmidi->private_data;

	spin_lock_irqsave(&mtp_card->spinlock, flags);
	if (up) {
#if 0	/* FIXME */
		if ((mtp_card->mode[port] & MTPAV_MODE_OUTPUT_TRIGGERED) == 0) {
			if (!mtp_card->istimer) {
				mtp_card->timer.function = snd_mtpav_output_timer;
				mtp_card->timer.data = (unsigned long) mtp_card;
				mtp_card->timer.expires = 1 + jiffies;
				add_timer(&mtp_card->timer);
			}
			mtp_card->istimer++;
		}
#endif
		mtp_card->mode[port] |= MTPAV_MODE_OUTPUT_TRIGGERED;
	} else {
		mtp_card->mode[port] &= ~MTPAV_MODE_OUTPUT_TRIGGERED;
	}
	spin_unlock_irqrestore(&mtp_card->spinlock, flags);

	if (up)
		snd_mtpav_output_write(rmidi);
}

//////////////////////////////////////////////////////////////////

static void snd_mtpav_inmidi_h(TSmtp * mcrd, U8 inbyte)
{
	snd_rawmidi_t *rmidi;
	//char ibyte = inbyte;
	int tesdev;

	if (mcrd == NULL)
		return;

	if (inbyte == 0xfe)
		return;

	if (mcrd->inmidistate == 0)	// awaiting command
	 {
		if (inbyte == 0xf5)	// MTP port #
		 {
			mcrd->inmidistate = 1;

		} else {

			if (mcrd->inmidiport >= NUMPORTS)
				return;

			rmidi = mcrd->rmidi[mcrd->inmidiport];

			if (rmidi == NULL)
				return;

			tesdev = rmidi->device;

			if (
				   (mcrd->
			mode[mcrd->inmidiport] & MTPAV_MODE_INPUT_OPENED)
				   != 0) {
				snd_rawmidi_receive(rmidi, &inbyte, 1);
				//printk( "rmidi: 0x%x tesdev: %d inbyte: 0x%x\n", rmidi, tesdev, inbyte );
			}
		}
	} else if (mcrd->inmidistate == 1) {
		mcrd->inmidiport = inbyte;
		if (mcrd->inmidiport > (NUMPORTS - 1))
			mcrd->inmidiport = (NUMPORTS - 1);	// limit input ports

		mcrd->inmidistate = 0;
		//snd_printk("cable %d\n", (int) mcrd->inmidiport);
	}
}

static void snd_mtpav_read_bytes(TSmtp * mcrd)
{
	U8 clrread, setread;
	U8 mtp_read_byte;
	U8 read_sr0, read_sr1, read_sr2, read_sr3;
	U8 cbyt;

	U8 sbyt = snd_mtpav_getreg(SREG);

	//printk("snd_mtpav_read_bytes() sbyt: 0x%x\n", sbyt);

	if (!(sbyt & SIGS_BYTE))
		return;

	cbyt = snd_mtpav_getreg(CREG);
	clrread = cbyt & (SIGC_READ ^ 0xff);
	setread = cbyt | SIGC_READ;

	do {

		snd_mtpav_mputreg(CREG, setread);
		read_sr0 = snd_mtpav_getreg(SREG);
		snd_mtpav_mputreg(CREG, clrread);

		snd_mtpav_mputreg(CREG, setread);
		read_sr1 = snd_mtpav_getreg(SREG);
		snd_mtpav_mputreg(CREG, clrread);

		snd_mtpav_mputreg(CREG, setread);
		read_sr2 = snd_mtpav_getreg(SREG);
		snd_mtpav_mputreg(CREG, clrread);

		snd_mtpav_mputreg(CREG, setread);
		read_sr3 = snd_mtpav_getreg(SREG);
		snd_mtpav_mputreg(CREG, clrread);

		mtp_read_byte = ((read_sr0 & (SIGS_IN0 | SIGS_IN1)) / 16);
		mtp_read_byte |=
		    (((read_sr1 & (SIGS_IN0 | SIGS_IN1)) / 16) << 2);
		mtp_read_byte |=
		    (((read_sr2 & (SIGS_IN0 | SIGS_IN1)) / 16) << 4);
		mtp_read_byte |=
		    (((read_sr3 & (SIGS_IN0 | SIGS_IN1)) / 16) << 6);

		snd_mtpav_inmidi_h(mcrd, mtp_read_byte);

		sbyt = snd_mtpav_getreg(SREG);

	} while (sbyt & SIGS_BYTE);
}

static void snd_mtpav_irqh(int irq, void *dev_id, struct pt_regs *regs)
{
	U32 flags = 0;
	TSmtp *mcard = (TSmtp *) dev_id;

	//printk("irqh()\n");
	spin_lock_irqsave(&mcard->spinlock, flags);
	snd_mtpav_read_bytes(mcard);
	spin_unlock_irqrestore(&mcard->spinlock, flags);
}

/////////////////////////////////////////////////////////////////
// get ISA resources

static int snd_mtpav_get_ISA(TSmtp * mcard)
{
	static long possible_irqs[] = { -1 };

	if (snd_register_ioport(mcard->card, snd_port, 3,
				"MotuMTPAV MIDI", &mcard->rioport) < 0) {
		snd_printk("MTVAP port 0x%x is busy\n", snd_port);
		return -EBUSY;
	}
	//snd_printk("snd_mtpav_register_resources requesting irq=%d\n", snd_irq);
	if (snd_register_interrupt(mcard->card, "MOTU MTPAV", snd_irq,
				   SND_IRQ_TYPE_ISA, snd_mtpav_irqh,
				   mcard, possible_irqs,
				   &mcard->irq) < 0)
		return -EBUSY;
	return 0;
}

/////////////////////////////////////////////////////////////////
// get RAWMIDI resources

static int snd_mtpav_get_RAWMIDI(TSmtp * mcard)
{
	U32 i;
	int rval = 0;
	snd_rawmidi_t *rawmidi;
	char rmidinames1[80];

	//printk("entering snd_mtpav_get_RAWMIDI\n");

	for (i = 0; i < NUMPORTS; i++) {
		sprintf(rmidinames1, "MTPAV Midiport %ld", i);

		if ((rval = snd_rawmidi_new(mcard->card, rmidinames1,
					    i, &mcard->rmidi[i])) < 0)
			return rval;

		rawmidi = mcard->rmidi[i];
		if (rawmidi == NULL)
			return 0;

		rawmidi->private_data = (void *) i;

		memcpy(&rawmidi->chn[SND_RAWMIDI_CHANNEL_OUTPUT].hw,
		       &snd_mtpav_output, sizeof(snd_mtpav_output));
		memcpy(&rawmidi->chn[SND_RAWMIDI_CHANNEL_INPUT].hw,
		       &snd_mtpav_input, sizeof(snd_mtpav_input));
		rawmidi->info_flags |=
		    SND_RAWMIDI_INFO_OUTPUT | SND_RAWMIDI_INFO_INPUT |
		    SND_RAWMIDI_INFO_DUPLEX;
		sprintf(rawmidi->name, "MotuMIDI %d", (int) i);
	}

	//printk("exiting snd_mtpav_get_RAWMIDI() \n");
	return 0;
}

/////////////////////////////////////////////////////////////////

static TSmtp *new_TSmtp(void)
{
	U32 i;
	TSmtp *ncrd = (TSmtp *) snd_kcalloc(sizeof(TSmtp), GFP_KERNEL);
	if (ncrd != NULL) {
		spin_lock_init(&ncrd->spinlock);

		ncrd->card = NULL;
		ncrd->rioport = NULL;
		ncrd->irq = NULL;

		ncrd->inmidiport = 0xffffffff;
		ncrd->inmidistate = 0;
		ncrd->outmidiport = 0xffffffff;

		for (i = 0; i < NUMPORTS; i++) {
			ncrd->rmidi[i] = NULL;
			ncrd->mode[i] = 0;
		}
	}
	return ncrd;
}

/////////////////////////////////////////////////////////////////

static void free_TSmtp(TSmtp * crd)
{
	if (crd != NULL)
		snd_kfree(crd);
}

/////////////////////////////////////////////////////////////////

static int __init alsa_card_mtpav_init(void)
{
	int err = 0;
	char longname_buffer[80];

	mtp_card = NULL;

	if (NUMPORTS > SND_RAWMIDI_DEVICES) {
		snd_printk("snd-card-mtpav ERROR: NUMPORTS in alsa-driver/include/mtpav.h (%d) is > SND_RAWMIDI_DEVICES in alsa-driver/include/rawmidi.h (%d)!\n", NUMPORTS, SND_RAWMIDI_DEVICES);
		return -1;
	}
	mtp_card = new_TSmtp();
	if (mtp_card == NULL)
		return -ENOMEM;

	mtp_card->card = snd_card_new(snd_index, snd_id,
				      snd_mtpav_use_inc,
				      snd_mtpav_use_dec);
	if (mtp_card->card == NULL) {
		free_TSmtp(mtp_card);
		return -ENOMEM;
	}

	mtp_card->card->type = SND_CARD_TYPE_MTPAV;
	strcpy(mtp_card->card->abbreviation, "mtpav");
	strcpy(mtp_card->card->shortname, "mtpav on parallel port");
	memset(longname_buffer, 0, sizeof(longname_buffer));
	sprintf(longname_buffer, "mtpav on parallel port at");

	err = snd_mtpav_get_ISA(mtp_card);
	//printk("snd_mtpav_get_ISA returned: %d\n", err);
	if (err < 0)
		goto __error;

	err = snd_mtpav_get_RAWMIDI(mtp_card);
	//snd_printk("snd_mtapv_get_RAWMIDI returned: %d\n", err);
	if (err < 0)
		goto __error;

	err = snd_card_register(mtp_card->card);	// dont snd_card_register until AFTER all cards reources done!

	//printk("snd_card_register returned %d\n", err);
	if (err < 0)
		goto __error;


	snd_mtpav_portscan();

	printk("Motu MidiTimePiece on parallel port irq: %d ioport: 0x%x\n", snd_irq, snd_port);

	return 0;

      __error:
	snd_card_free(mtp_card->card);
	free_TSmtp(mtp_card);
	return err;
}

/////////////////////////////////////////////////////////////////

static void __exit alsa_card_mtpav_exit(void)
{
	if (mtp_card == NULL)
		return;
	if (mtp_card->card)
		snd_card_unregister(mtp_card->card);
	free_TSmtp(mtp_card);
}

/////////////////////////////////////////////////////////////////

static void snd_mtpav_use_inc(snd_card_t * card)
{
	MOD_INC_USE_COUNT;
}

/////////////////////////////////////////////////////////////////

static void snd_mtpav_use_dec(snd_card_t * card)
{
	MOD_DEC_USE_COUNT;
}

/////////////////////////////////////////////////////////////////

module_init(alsa_card_mtpav_init)
module_exit(alsa_card_mtpav_exit)
