/*     
 **********************************************************************
 *     irqmgr.c - IRQ manager for emu10k1 driver 
 *     Copyright 1999, 2000 Creative Labs, Inc. 
 * 
 ********************************************************************** 
 * 
 *     Date                 Author          Summary of changes 
 *     ----                 ------          ------------------ 
 *     October 20, 1999     Bertrand Lee    base code release 
 * 
 ********************************************************************** 
 * 
 *     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 "irqmgr.h"
#include "mycommon.h"

/* Interrupt handler */
/* FIXME: We should hook all handlers no matter what.
 * Testing for the presence of a handler takes too long. */
void emu10k1_interrupt(int nIRQ, void *pvDevID, struct pt_regs *pRegs)
{
	struct sblive_irq *irq_ptr = (struct sblive_irq *) pvDevID;
        struct sblive_hw *sb_hw = irq_ptr->sb_hw;
        u32 irqstatus;
        u32 ptr;

        /* FIXME: Do we really need to _ever_ check this, let alone
         * at every single interrupt??? * 
	 * yes if we share irqs - rsousa */
        if (sb_hw->hw_irq.ID != EMU10K1_INTERRUPT_ID)
          return;

        DPD("emu10k1_interrupt called, nIRQ =  %u\n", nIRQ);

        /* Preserve PTR register */
        ptr = sblive_readfn0(sb_hw, PTR);

        /*
           ** NOTE :
           ** We do a 'while loop' here cos on certain machines, with both
           ** playback and recording going on at the same time, IRQs will
           ** stop coming in after a while. Checking IPND indeed shows that
           ** there are interrupts pending but the PIC says no IRQs pending.
           ** I suspect that some boards need edge-triggered IRQs but are not
           ** getting that condition if we don't completely clear the IPND
           ** (make sure no more interrupts are pending).
           ** - Eric
         */

        irqstatus = sblive_readfn0(sb_hw, IPR);

        do
        {
                DPD("irq status %x\n", irqstatus);

                if (irqstatus & IRQTYPE_PCIBUSERROR)
                {
                        if (sb_hw->PCIBusErrorCallback != NULL)
                          sb_hw->PCIBusErrorCallback(0, sb_hw->pcierrorrefdata, irqstatus & IRQTYPE_PCIBUSERROR);
                        else
                          sblive_irqmgrDisableIrq(sb_hw, INTE_PCIERRORENABLE);
                }

                if (irqstatus & IRQTYPE_MIXERBUTTON)
                {
                        if (sb_hw->MixerButtonCallback != NULL)
                          sb_hw->MixerButtonCallback(0, sb_hw->mixerbuttonrefdata, irqstatus & IRQTYPE_MIXERBUTTON);
                        else
                          sblive_irqmgrDisableIrq(sb_hw, INTE_VOLINCRENABLE | INTE_VOLDECRENABLE | INTE_MUTEENABLE);
                }

                if (irqstatus & IRQTYPE_VOICE)
                {
                        if (sb_hw->VoiceCallback != NULL)
                          sb_hw->VoiceCallback(0, sb_hw->voicerefdata, irqstatus & IRQTYPE_VOICE);
                }

                if (irqstatus & IRQTYPE_RECORD)
                {
                        if (sb_hw->RecordCallback != NULL)
                          sb_hw->RecordCallback(0, sb_hw->recordrefdata, irqstatus & IRQTYPE_RECORD);
                        else
                          sblive_irqmgrDisableIrq(sb_hw, INTE_ADCBUFENABLE);
                }

                if (irqstatus & IRQTYPE_MPUOUT)
                {
                        if (sb_hw->MpuOutCallback != NULL)
                          sb_hw->MpuOutCallback(0, sb_hw->mpuoutrefdata, irqstatus & IRQTYPE_MPUOUT);
                        else
                          sblive_irqmgrDisableIrq(sb_hw, INTE_MIDITXENABLE);
                }

                if (irqstatus & IRQTYPE_MPUIN)
                {
                        if (sb_hw->MpuInCallback != NULL)
                          sb_hw->MpuInCallback(0, sb_hw->mpuinrefdata, irqstatus & IRQTYPE_MPUIN);
                        else
                          sblive_irqmgrDisableIrq(sb_hw, INTE_MIDIRXENABLE);
                }

                if (irqstatus & IRQTYPE_TIMER)
                {
                        if (sb_hw->TimerCallback != NULL)
                          sb_hw->TimerCallback(0, sb_hw->timerrefdata, irqstatus & IRQTYPE_TIMER);
                        else
                          sblive_irqmgrDisableIrq(sb_hw, INTE_INTERVALTIMERENB);
                }

                if (irqstatus & IRQTYPE_SPDIF)
                {
                        if (sb_hw->SPDIFCallback != NULL)
                          sb_hw->SPDIFCallback(0, sb_hw->spdifrefdata, irqstatus & IRQTYPE_SPDIF);
                        else
                          sblive_irqmgrDisableIrq(sb_hw, INTE_GPSPDIFENABLE | INTE_CDSPDIFENABLE);
                }

                if (irqstatus & IRQTYPE_DSP)
                {
                        if (sb_hw->DSPCallback != NULL)
                          sb_hw->DSPCallback(0, sb_hw->DSPrefdata, irqstatus & IRQTYPE_DSP);
                        else
                          sblive_irqmgrDisableIrq(sb_hw, INTE_FXDSPENABLE);
                }

                sblive_writefn0(sb_hw, IPR, irqstatus);

        } while ((irqstatus = sblive_readfn0(sb_hw, IPR)));

        sblive_writefn0(sb_hw, PTR, ptr);

	return;
}

/************************************************************************
*
*   int    sblive_irqmgrInit(struct sblive_hw * sb_hw)
*
*   ENTRY
*       sb_hw -   Card object
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Initialization
*
************************************************************************/
int sblive_irqmgrInit(struct sblive_irq *irq_ptr)
{
	unsigned long flags;

        irq_ptr->status = 0;
        irq_ptr->ID = EMU10K1_INTERRUPT_ID;

        flags = SA_INTERRUPT | SA_SHIRQ;

        /* Reserve IRQ Line */
        if (request_irq(irq_ptr->irq, emu10k1_interrupt, flags, "emu10k1", irq_ptr))
        {
                DPD("emu10k1: irq %u in use\n", (unsigned int) irq_ptr->irq);
                irq_ptr->status &= ~FLAGS_ENABLED;
		DPF("Error initializing IRQ\n");
                return CTSTATUS_ERROR;
        }

        irq_ptr->status |= (FLAGS_ENABLED | FLAGS_AVAILABLE);

	if (irq_ptr->status & (FLAGS_ENABLED | FLAGS_AVAILABLE))
          irq_ptr->status &= (~FLAGS_AVAILABLE);
        else
	{
	  DPF("IRQ acquire failure\n");
          return CTSTATUS_INUSE;
	}

        DPD("IRQ : Acquired channel %u\n", irq_ptr->irq);
        return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int    sblive_irqmgrExit(struct sblive_irq *irq_ptr)
*
*   ENTRY
*       irq_ptr -   Card irq object
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Shutdown code
*
************************************************************************/
int sblive_irqmgrExit(struct sblive_irq *irq_ptr)
{
	irq_ptr->status |= FLAGS_AVAILABLE;
	free_irq(irq_ptr->irq, irq_ptr);
        irq_ptr->status &= ~FLAGS_ENABLED;

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int    sblive_irqmgrInstallIrqHandler(struct sblive_hw * sb_hw,
*                                       u32 irqtype,
*                                       CALLBACKFN callback,
*                                       u32 refdata)
*
*   ENTRY
*       sb_hw -   Card object
*       wIrqType-   type of service to install handler for
*       callback
*               -   irq service handler
*       refdata
*               -   data to call hadler with
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Used to install handlers for various irq services
*
************************************************************************/
int sblive_irqmgrInstallIrqHandler(struct sblive_hw *sb_hw, u32 irqtype, CALLBACKFN callback, u32 refdata)
{
	DPD("sblive_irqmgrInstallIrqHandler %x\n", irqtype);

	switch (irqtype) 
	{
	case IRQTYPE_PCIBUSERROR:
		if (sb_hw->PCIBusErrorCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->PCIBusErrorCallback = callback;
		sb_hw->pcierrorrefdata = refdata;
		break;

	case IRQTYPE_MIXERBUTTON:
		if (sb_hw->MixerButtonCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->MixerButtonCallback = callback;
		sb_hw->mixerbuttonrefdata = refdata;
		break;

	case IRQTYPE_VOICE:
		if (sb_hw->VoiceCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->VoiceCallback = callback;
		sb_hw->voicerefdata = refdata;
		break;

	case IRQTYPE_RECORD:
		if (sb_hw->RecordCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->RecordCallback = callback;
		sb_hw->recordrefdata = refdata;
		break;

	case IRQTYPE_MPUOUT:
		if (sb_hw->MpuOutCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->MpuOutCallback = callback;
		sb_hw->mpuoutrefdata = refdata;
		break;

	case IRQTYPE_MPUIN:
		if (sb_hw->MpuInCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->MpuInCallback = callback;
		sb_hw->mpuinrefdata = refdata;
		break;

	case IRQTYPE_TIMER:
		if (sb_hw->TimerCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->TimerCallback = callback;
		sb_hw->timerrefdata = refdata;
		break;

	case IRQTYPE_SPDIF:
		if (sb_hw->SPDIFCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->SPDIFCallback = callback;
		sb_hw->spdifrefdata = refdata;
		break;

	case IRQTYPE_DSP:
		if (sb_hw->DSPCallback != NULL)
		  return CTSTATUS_INUSE;
		sb_hw->DSPCallback = callback;
		sb_hw->DSPrefdata = refdata;
		break;

	default:
		return CTSTATUS_ERROR;
	}

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int    sblive_irqmgrUninstallIrqHandler(struct sblive_hw * sb_hw,
*                                         u32 irqtype)
*
*   ENTRY
*       sb_hw -   Card object
*       wIrqType-   type of service to remove handler for
*
*   RETURNS
*       SUCCESS -   CTSTATUS_SUCCESS
*       FAILURE -   CTSTATUS_ERROR
*
*   ABOUT
*       Used to remove previously installed handlers
*
************************************************************************/
int sblive_irqmgrUninstallIrqHandler(struct sblive_hw *sb_hw, u32 irqtype)
{
	unsigned long flags;
	
	DPD("sblive_irqmgrUninstallIrqHandler %x\n", irqtype);

	spin_lock_irqsave(&sb_hw->emu_lock, flags);

	switch (irqtype) 
	{
	case IRQTYPE_PCIBUSERROR:
		sb_hw->PCIBusErrorCallback = NULL;
		sb_hw->pcierrorrefdata = 0;
		break;

	case IRQTYPE_MIXERBUTTON:
		sb_hw->MixerButtonCallback = NULL;
		sb_hw->mixerbuttonrefdata = 0;
		break;

	case IRQTYPE_VOICE:
		sb_hw->VoiceCallback = NULL;
		sb_hw->voicerefdata = 0;
		break;

	case IRQTYPE_RECORD:
		sb_hw->RecordCallback = NULL;
		sb_hw->recordrefdata = 0;
		break;

	case IRQTYPE_MPUOUT:
		sb_hw->MpuOutCallback = NULL;
		sb_hw->mpuoutrefdata = 0;
		break;

	case IRQTYPE_MPUIN:
		sb_hw->MpuInCallback = NULL;
		sb_hw->mpuinrefdata = 0;
		break;

	case IRQTYPE_TIMER:
		sb_hw->TimerCallback = NULL;
		sb_hw->timerrefdata = 0;
		break;

	case IRQTYPE_SPDIF:
		sb_hw->SPDIFCallback = NULL;
		sb_hw->spdifrefdata = 0;
		break;

	case IRQTYPE_DSP:
		sb_hw->DSPCallback = NULL;
		sb_hw->DSPrefdata = 0;
		break;

	default:
		spin_unlock_irqrestore(&sb_hw->emu_lock, flags);
		return CTSTATUS_ERROR;
	}

	spin_unlock_irqrestore(&sb_hw->emu_lock, flags);

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int    sblive_irqmgrEnableIrq(struct sblive_hw * sb_hw, u16 wIrqType)
*
*   ENTRY
*       sb_hw -   Card object
*       wIrqType-   type of service to enable interrupt for
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Enables the specified irq service
*
************************************************************************/
int sblive_irqmgrEnableIrq(struct sblive_hw *sb_hw, u32 irqtype)
{
	/*
	 * TODO :
	 * put protection here so that we don't accidentally
	 * screw-up another cardxxx objects irqs
	 */

	DPD("sblive_irqmgrEnableIrq %x\n", irqtype);
	sblive_wrtmskfn0(sb_hw, INTE, irqtype, ENABLE);

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int    sblive_irqmgrDisableIrq(struct sblive_hw * sb_hw, u16 wIrqType)
*
*   ENTRY
*       sb_hw -   Card object
*       wIrqType-   type of service to disable interrupt for
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Disables the specified irq service
*
************************************************************************/

int sblive_irqmgrDisableIrq(struct sblive_hw *sb_hw, u32 irqtype)
{
	/*
	 * TODO :
	 * put protection here so that we don't accidentally
	 * screw-up another cardxxx objects irqs
	 */

	DPD("sblive_irqmgrDisableIrq %x\n", irqtype);
	sblive_wrtmskfn0(sb_hw, INTE, irqtype, DISABLE);

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int    sblive_irqmgrEnableVoiceIrq(struct sblive_hw * sb_hw, u32 voicenum)
*
*   ENTRY
*       sb_hw -   Card object
*       voicenum
*               -   voice to enable interrupt for
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Enables the specified voice irq service
*
************************************************************************/

int sblive_irqmgrEnableVoiceIrq(struct sblive_hw *sb_hw, u32 voicenum)
{
	/*
	 * TODO :
	 * put protection here so that we don't accidentally
	 * screw-up another cardxxx objects irqs
	 */

	DPD("sblive_irqmgrEnableVoiceIrq %x\n", voicenum);
	halVoiceIntrEnable(sb_hw, voicenum);

	return CTSTATUS_SUCCESS;
}


/************************************************************************
*
*   int    sblive_irqmgrDisableVoiceIrq(struct sblive_hw * sb_hw, u32 voicenum)
*
*   ENTRY
*       sb_hw -   Card object
*       voicenum
*               -   voice to disable interrupt for
*
*   RETURNS
*       Always returns CTSTATUS_SUCCESS
*
*   ABOUT
*       Disables the specified voice irq service
*
************************************************************************/

int sblive_irqmgrDisableVoiceIrq(struct sblive_hw *sb_hw, u32 voicenum)
{
	/*
	 * TODO :
	 * put protection here so that we don't accidentally
	 * screw-up another cardxxx objects irqs
	 */

	DPD("sblive_irqmgrDisableVoiceIrq %x\n", voicenum);
	halVoiceIntrDisable(sb_hw, voicenum);

	return CTSTATUS_SUCCESS;
}
