/*     
 **********************************************************************
 *     cardmo.c - MIDI UART output HAL for emu10k1 driver 
 *     Copyright 1999, 2000 Creative Labs, Inc. 
 * 
 ********************************************************************** 
 * 
 *     Date                 Author          Summary of changes 
 *     ----                 ------          ------------------ 
 *     October 20, 1999     Bertrand Lee    base code release 
 *     November 2, 1999     Alan Cox        cleaned up
 * 
 ********************************************************************** 
 * 
 *     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 "hwaccess.h"
#include "cardmo.h"

/****************************************************************************/
/*int    sblive_mpuoutInit(PICARDMIDI pICardMidi, PICARD pICard )           */
/*                                                                          */
/*Function:   Initialize the parameters + reset the MPU port                */
/****************************************************************************/
int sblive_mpuoutInit(struct sblive_mpuout *card_mpuout, struct sblive_hw *sb_hw)
{
	u8 name[128];
	
	DPF("sblive_mpuoutInit\n");

	memset(card_mpuout, 0, sizeof(struct sblive_mpuout));

	card_mpuout->intr = TRUE;
	card_mpuout->status = FLAGS_AVAILABLE;
	card_mpuout->state = CARDMIDIOUT_STATE_DEFAULT;
	card_mpuout->caps.cbsize = sizeof(struct midi_caps);
	card_mpuout->caps.support = MIDICAPS_OUTPUT;
	card_mpuout->caps.technology = MT_MIDIPORT;
	card_mpuout->caps.product = MM_CREATIVE_MIDIOUT;
	card_mpuout->caps.manufacturer = MM_CREATIVE;
	card_mpuout->caps.voices = 0;
	card_mpuout->caps.notes = 0;
	card_mpuout->caps.channelmask = 0xffff;
	card_mpuout->caps.caps = CARDMIDI_OUT;

	card_mpuout->dpc.is_active = FALSE;
	card_mpuout->dpc.refdata = (u32) sb_hw;
	card_mpuout->dpc.DPCCallBackFn = sblive_mpuoutDpcCallback;

	spin_lock_init(&card_mpuout->lock);

	/* Set up the IRQ handler */
	if (sblive_irqmgrInstallIrqHandler
	    (sb_hw,(u16)(IRQTYPE_MPUOUT),
	     sblive_mpuoutIrqCallback, (u32)sb_hw) 
	    != CTSTATUS_SUCCESS) 
	{
		DPF("cardmo.c: IRQ in use\n");
		return CTSTATUS_ALLOCATED;
	} else 
	{
		DPF("cardmo.c: IRQ installed\n");
	}

	strcpy(name, IDS_EMU_MIDIOUT_PNAME);

	/* Fill in card caps */
	sprintf(card_mpuout->caps.MIDIname, "%s [%lx]", name, sb_hw->hwaddr);

	/* Reset the MPU port */
	if (hwmpuReset(sb_hw) != CTSTATUS_SUCCESS) 
	{
		DPF("cardmo.c: MPU hardware reset failure\n");
		return CTSTATUS_NOTENABLED;
	}
	
	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/*int    sblive_mpuoutExit(struct sblive_mpuout *card_mpuout)               */
/*                                                                          */
/*Function:   Disable the IRQ TX and uninstall the IRQ handler              */
/****************************************************************************/
int sblive_mpuoutExit(struct sblive_hw *sb_hw)
{
	struct sblive_mpuout *card_mpuout = sb_hw->card_mpuout;
	
	DPF("sblive_mpuoutExit\n");

	sblive_irqmgrDisableIrq(sb_hw, INTE_MIDITXENABLE);

	sblive_irqmgrUninstallIrqHandler(sb_hw, IRQTYPE_MPUOUT);

	card_mpuout->status |= FLAGS_AVAILABLE;
	card_mpuout->state = CARDMIDIOUT_STATE_DEFAULT;

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* int    sblive_mpuoutOpen(PICARDMIDI pICardMidi,                          */
/*                            struct midi_openinfo *openinfo,               */
/*                            u32 *handle)                                  */
/*                                                                          */
/* Function:   Installs the IRQ handler for the MPU out port                */
/*             and initialize parameters                                    */
/****************************************************************************/
int sblive_mpuoutOpen(struct sblive_hw *sb_hw, struct midi_openinfo *openinfo, u32 *handle)
{
	struct sblive_mpuout *card_mpuout = sb_hw->card_mpuout; 
	
	DPF("sblive_mpuoutOpen\n");

	if (!(card_mpuout->status & FLAGS_AVAILABLE))
	  return CTSTATUS_INUSE;

	/* Copy open info and mark channel as in use */
	card_mpuout->intr = TRUE;
	card_mpuout->openinfo = *openinfo;
	card_mpuout->status &= ~FLAGS_AVAILABLE;
	card_mpuout->laststatus = 0x80;
	card_mpuout->firstmidiq = NULL;
	card_mpuout->lastmidiq = NULL;

	hwmpuReset(sb_hw);
	hwmpuAcquire(sb_hw);

	*handle = (u32)card_mpuout;

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* int    sblive_mpuoutClose(PICARDMIDI pICardMidi,                         */
/*                             u32 handle)                                  */
/*                                                                          */
/* Function:   If local buffer is not empty,                                */
/*             return CTSTATUS_STILLPLAYING                                 */
/*             Otherwise, disable and uninstall TX IRQ                      */
/****************************************************************************/
int sblive_mpuoutClose(struct sblive_hw *sb_hw, u32 handle)
{
	struct sblive_mpuout *card_mpuout = sb_hw->card_mpuout;
	
	DPF("sblive_mpuoutClose\n");

	if (card_mpuout->firstmidiq) 
	{
		DPF("Cannot close - buffers not empty\n");
		return CTSTATUS_STILLPLAYING;
	}
	
	sblive_irqmgrDisableIrq(sb_hw, INTE_MIDITXENABLE);
	
	hwmpuRelease(sb_hw);

	card_mpuout->status |= FLAGS_AVAILABLE;

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* int    sblive_mpuoutAddBuffer(PICARDMIDI pICardMidi,                     */
/*                                 u32 handle,                              */
/*                                 struct midi_hdr *INFO midihdrInfo)       */
/*                                                                          */
/* Function:   If there isn't enough buffer space, reject Midi Buffer.      */
/*             Otherwise, disable TX, create object to hold Midi            */
/*             Buffer, update buffer flags and other parameters             */
/*             before enabling TX again.                                    */
/****************************************************************************/
int sblive_mpuoutAddBuffer(struct sblive_hw *sb_hw, u32 handle, struct midi_hdr *midihdr)
{
	struct sblive_mpuout *card_mpuout = sb_hw->card_mpuout;
	struct midi_queue *midiq;
	unsigned long flags;

	DPF("sblive_mpuoutAddBuffer\n");

	if (card_mpuout->state == CARDMIDIOUT_STATE_SUSPEND)
	  return CTSTATUS_SUCCESS;

	midihdr->flags |= MIDIBUF_INQUEUE;
	midihdr->flags &= ~MIDIBUF_DONE;

	midiq = (struct midi_queue *) kmalloc(sizeof(struct midi_queue), GFP_KERNEL);
	if (midiq == NULL) 
	{
		/* Message lost */
		return CTSTATUS_NOMEMORY;
	}
	
	midiq->next = NULL;
	midiq->qtype = 1;
	midiq->length = midihdr->bufferlength;
	midiq->sizeLeft = midihdr->bufferlength;
	midiq->midibyte = midihdr->lpData;

	midiq->refdata = (u32) midihdr;

	spin_lock_irqsave(&card_mpuout->lock, flags);

	if (card_mpuout->firstmidiq == NULL) 
	{
		card_mpuout->firstmidiq = midiq;
		card_mpuout->lastmidiq = midiq;
	} else 
	{
		(card_mpuout->lastmidiq)->next = midiq;
		card_mpuout->lastmidiq = midiq;
	}

	card_mpuout->intr = FALSE;
	
	sblive_irqmgrEnableIrq(sb_hw, INTE_MIDITXENABLE);

	spin_unlock_irqrestore(&card_mpuout->lock, flags);

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* int    sblive_mpuoutWriteShortData(PICARDMIDI pICardMidi,                */
/*                                      u32 handle,                         */
/*                                      u32 midimsg)                        */
/*                                                                          */
/* Function:   Adds the short message to our local buffer                   */
/* Note:       This writeout must succeed at all cost ... else UART output  */
/*             will sound wierd at a sound module.                          */
/****************************************************************************/
int sblive_mpuoutWriteShortData(struct sblive_hw *sb_hw, u32 handle, u32 midimsg)
{
	struct sblive_mpuout *card_mpuout = sb_hw->card_mpuout;
	int msglen;
	struct midi_queue *midiq;
	int status;
	unsigned long flags;


	if (card_mpuout->state == CARDMIDIOUT_STATE_SUSPEND)
	  return CTSTATUS_SUCCESS;

	if (midimsg & 0x80) 
	{
		if (((u8) midimsg) < 0xF0) 
		{
			card_mpuout->laststatus = (u8) midimsg;
			msglen = gabMsgLenChannel[((midimsg & 0xF0) - 0x80) >> 4];
		} else
		  msglen = gabMsgLenSystem[midimsg & 0x0F];
	} else 
	{
		if (card_mpuout->laststatus)
		  msglen = gabMsgLenChannel[((card_mpuout->laststatus & 0xF0) - 0x80) >> 4];
		else 
		{
			DPD("sblive_mpuoutWriteShortData error!!: midimsg = %x\n", midimsg);
			return CTSTATUS_ERROR;
		}
		--msglen;
	}

	if (card_mpuout->lastmidiq == NULL) 
	{
		unsigned long flags;
		/* Wait until TX interrupt has occurred.
		 * This means that the FIFO is empty.
		 */
		while (card_mpuout->intr != TRUE);

		spin_lock_irqsave(&card_mpuout->lock, flags);

		while (msglen--) 
		{
			status = hwmpuWriteData(sb_hw, (u8)midimsg);

			if (status != CTSTATUS_SUCCESS) 
			{
				DPD("sblive_mpuoutWriteShortData error!!: byte = %x\n", (u8) midimsg);
				DPD("UART BYTE OUT (MISSED) = %x\n", (u8)midimsg);
				msglen++;
			} else 
			{
				DPD("UART BYTE OUT = %x\n", (u8)midimsg);
				midimsg >>= 8;
			}
		}

		card_mpuout->intr = FALSE;
		sblive_irqmgrEnableIrq(sb_hw, INTE_MIDITXENABLE);
		spin_unlock_irqrestore(&card_mpuout->lock, flags);

		return CTSTATUS_SUCCESS;
	}
	
	midiq = (struct midi_queue *) kmalloc(sizeof(struct midi_queue), GFP_KERNEL);
	if (midiq == NULL) 
	{
		/* Message lost */
		DPD("sblive_mpuoutWriteShortData error!!: midimsg = %x\n", midimsg);
		return CTSTATUS_NOMEMORY;
	}
	
	midiq->next = NULL;
	midiq->qtype = 0;
	midiq->length = msglen;
	midiq->sizeLeft = msglen;
	midiq->midibyte = (u8 *) & midiq->refdata;
	midiq->refdata = midimsg;

	spin_lock_irqsave(&card_mpuout->lock, flags);

	if (card_mpuout->firstmidiq == NULL) 
	{
		card_mpuout->lastmidiq = midiq;
		card_mpuout->firstmidiq = midiq;
	} else 
	{
		(card_mpuout->lastmidiq)->next = midiq;
		card_mpuout->lastmidiq = midiq;
	}

	card_mpuout->intr = FALSE;
	sblive_irqmgrEnableIrq(sb_hw, INTE_MIDITXENABLE);
	spin_unlock_irqrestore(&card_mpuout->lock, flags);

	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* int sblive_mpuoutDpcCallback ()                                          */
/****************************************************************************/
int sblive_mpuoutDpcCallback(unsigned long refdata, unsigned long param1, unsigned long param2)
{
	int cByteSent = 0;
	int status;
	struct midi_queue *midiq;
	struct midi_queue *doneq = NULL;
	struct sblive_hw *sb_hw = (struct sblive_hw *) refdata;
	struct sblive_mpuout *card_mpuout = sb_hw->card_mpuout;

	while (card_mpuout->firstmidiq) 
	{
		midiq = card_mpuout->firstmidiq;
		
		while (cByteSent < 4 && midiq->sizeLeft) 
		{
			status = hwmpuWriteData(sb_hw, *midiq->midibyte);

			if (status == CTSTATUS_SUCCESS) 
			{
				++cByteSent;
				--midiq->sizeLeft;
				++midiq->midibyte;
			} else 
			{
				DPF("sblive_mpuoutDpcCallback error!!\n");
			}
		}
		
		if (midiq->sizeLeft == 0) 
		{
			if (doneq == NULL)
			  doneq = midiq;
			card_mpuout->firstmidiq = midiq->next;
		} else
		  break;
	}

	if (card_mpuout->firstmidiq == NULL)
	  card_mpuout->lastmidiq = NULL;

	if (doneq) 
	{
		while (doneq != card_mpuout->firstmidiq) 
		{
			u32 callback_msg[3];

			midiq = doneq;
			doneq = midiq->next;
			
			if (midiq->qtype) 
			{
				callback_msg[0] = 0;
				callback_msg[1] = midiq->length;
				callback_msg[2] = midiq->refdata;
				
				card_mpuout->openinfo.CallbackFn(ICARDMIDI_OUTLONGDATA, card_mpuout->openinfo.refdata, (u32)callback_msg);
			} else 
			  if (((u8) midiq->refdata) < 0xF0 &&
			      ((u8) midiq->refdata) > 0x7F)
			    card_mpuout->laststatus = (u8) midiq->refdata;

			kfree(midiq);
		}
	}
	
	if (card_mpuout->firstmidiq || cByteSent) 
	{
		card_mpuout->intr = FALSE;
		sblive_irqmgrEnableIrq(sb_hw, INTE_MIDITXENABLE);
	}
	
	return CTSTATUS_SUCCESS;
}


/****************************************************************************/
/* int    sblive_mpuoutIrqCallback(unsigned long event,                     */
/*                                   unsigned long refdata,                 */
/*                                   unsigned long param)                   */
/*                                                                          */
/* Function:   IRQ callback handler routine for the MPU out port            */
/****************************************************************************/
int sblive_mpuoutIrqCallback(unsigned long event, unsigned long refdata, unsigned long param)
{
	struct sblive_hw *sb_hw = (struct sblive_hw *) refdata;
	struct sblive_mpuout *card_mpuout = sb_hw->card_mpuout;

	/* Called during ISR. The data & code touched are:
	 * 1. card_mpuout
	 * 2. sblive_irqmgrDisableIrq()
	 */

	DPF("sblive_mpuoutIrqCallback\n");

	card_mpuout->intr = TRUE;
	sblive_irqmgrDisableIrq(sb_hw, INTE_MIDITXENABLE);
	osScheduleDPC(&card_mpuout->dpc);

	return CTSTATUS_SUCCESS;
}
