/****************************************************************************
*
*    Copyright (C) 2005 - 2013 by Vivante Corp.
*
*    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 "gc_hal_kernel_precomp.h"

#if gcdENABLE_VG

/******************************************************************************\
*********************** Support Functions and Definitions **********************
\******************************************************************************/

/* Interruot statistics will be accumulated if not zero. */
#define gcmENABLE_INTERRUPT_STATISTICS 0

#define _GC_OBJ_ZONE    gcvZONE_INTERRUPT

/* Object structure. */
struct _gckVGINTERRUPT
{
    /* Object. */
    gcsOBJECT                   object;

    /* gckVGKERNEL pointer. */
    gckVGKERNEL                 kernel;

    /* gckOS pointer. */
    gckOS                       os;

    /* Interrupt handlers. */
    gctINTERRUPT_HANDLER        handlers[32];

    /* Main interrupt handler thread. */
    gctTHREAD                   handler;
    gctBOOL                     terminate;

    /* Interrupt FIFO. */
    gctSEMAPHORE                fifoValid;
    gctUINT32                   fifo[256];
    gctUINT                     fifoItems;
    gctUINT8                    head;
    gctUINT8                    tail;

    /* Interrupt statistics. */
#if gcmENABLE_INTERRUPT_STATISTICS
    gctUINT                     maxFifoItems;
    gctUINT                     fifoOverflow;
    gctUINT                     maxSimultaneous;
    gctUINT                     multipleCount;
#endif
};


/*******************************************************************************
**
**  _ProcessInterrupt
**
**  The interrupt processor.
**
**  INPUT:
**
**      ThreadParameter
**          Pointer to the gckVGINTERRUPT object.
**
**  OUTPUT:
**
**      Nothing.
*/

#if gcmENABLE_INTERRUPT_STATISTICS
static void
_ProcessInterrupt(
    gckVGINTERRUPT Interrupt,
    gctUINT_PTR TriggeredCount
    )
#else
static void
_ProcessInterrupt(
    gckVGINTERRUPT Interrupt
    )
#endif
{
    gceSTATUS status;
    gctUINT32 triggered;
    gctUINT i;

    /* Advance to the next entry. */
    Interrupt->tail      += 1;
    Interrupt->fifoItems -= 1;

    /* Get the interrupt value. */
    triggered = Interrupt->fifo[Interrupt->tail];
    gcmkASSERT(triggered != 0);

    gcmkTRACE_ZONE(
        gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
        "%s: triggered=0x%08X\n",
        __FUNCTION__,
        triggered
        );

    /* Walk through all possible interrupts. */
    for (i = 0; i < gcmSIZEOF(Interrupt->handlers); i += 1)
    {
        /* Test if interrupt happened. */
        if ((triggered & 1) == 1)
        {
#if gcmENABLE_INTERRUPT_STATISTICS
            if (TriggeredCount != gcvNULL)
            {
                (* TriggeredCount) += 1;
            }
#endif

            /* Make sure we have valid handler. */
            if (Interrupt->handlers[i] == gcvNULL)
            {
                gcmkTRACE(
                    gcvLEVEL_ERROR,
                    "%s: Interrupt %d isn't registered.\n",
                    __FUNCTION__, i
                    );
            }
            else
            {
                gcmkTRACE_ZONE(
                    gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
                    "%s: interrupt=%d\n",
                    __FUNCTION__,
                    i
                    );

                /* Call the handler. */
                status = Interrupt->handlers[i] (Interrupt->kernel);

                if (gcmkIS_ERROR(status))
                {
                    /* Failed to signal the semaphore. */
                    gcmkTRACE(
                        gcvLEVEL_ERROR,
                        "%s: Error %d incrementing the semaphore #%d.\n",
                        __FUNCTION__, status, i
                        );
                }
            }
        }

        /* Next interrupt. */
        triggered >>= 1;

        /* No more interrupts to handle? */
        if (triggered == 0)
        {
            break;
        }
    }
}


/*******************************************************************************
**
**  _MainInterruptHandler
**
**  The main interrupt thread serves the interrupt FIFO and calls registered
**  handlers for the interrupts that occured. The handlers are called in the
**  sequence interrupts occured with the exception when multiple interrupts
**  occured at the same time. In that case the handler calls are "sorted" by
**  the interrupt number therefore giving the interrupts with lower numbers
**  higher priority.
**
**  INPUT:
**
**      ThreadParameter
**          Pointer to the gckVGINTERRUPT object.
**
**  OUTPUT:
**
**      Nothing.
*/

static gctTHREADFUNCRESULT gctTHREADFUNCTYPE
_MainInterruptHandler(
    gctTHREADFUNCPARAMETER ThreadParameter
    )
{
    gceSTATUS status;
    gckVGINTERRUPT interrupt;

#if gcmENABLE_INTERRUPT_STATISTICS
    gctUINT count;
#endif

    /* Cast the object. */
    interrupt = (gckVGINTERRUPT) ThreadParameter;

    /* Enter the loop. */
    while (gcvTRUE)
    {
        /* Wait for an interrupt. */
        status = gckOS_DecrementSemaphore(interrupt->os, interrupt->fifoValid);

        /* Error? */
        if (gcmkIS_ERROR(status))
        {
            break;
        }

        /* System termination request? */
        if (status == gcvSTATUS_TERMINATE)
        {
            break;
        }

        /* Driver is shutting down? */
        if (interrupt->terminate)
        {
            break;
        }

#if gcmENABLE_INTERRUPT_STATISTICS
        /* Reset triggered count. */
        count = 0;

        /* Process the interrupt. */
        _ProcessInterrupt(interrupt, &count);

        /* Update conters. */
        if (count > interrupt->maxSimultaneous)
        {
            interrupt->maxSimultaneous = count;
        }

        if (count > 1)
        {
            interrupt->multipleCount += 1;
        }
#else
        /* Process the interrupt. */
        _ProcessInterrupt(interrupt);
#endif
    }

    return 0;
}


/*******************************************************************************
**
**  _StartInterruptHandler / _StopInterruptHandler
**
**  Main interrupt handler routine control.
**
**  INPUT:
**
**      ThreadParameter
**          Pointer to the gckVGINTERRUPT object.
**
**  OUTPUT:
**
**      Nothing.
*/

static gceSTATUS
_StartInterruptHandler(
    gckVGINTERRUPT Interrupt
    )
{
    gceSTATUS status, last;

    do
    {
        /* Objects must not be already created. */
        gcmkASSERT(Interrupt->fifoValid == gcvNULL);
        gcmkASSERT(Interrupt->handler   == gcvNULL);

        /* Reset the termination request. */
        Interrupt->terminate = gcvFALSE;

#if !gcdENABLE_INFINITE_SPEED_HW
        /* Construct the fifo semaphore. */
        gcmkERR_BREAK(gckOS_CreateSemaphoreVG(
            Interrupt->os, &Interrupt->fifoValid
            ));

        /* Start the interrupt handler thread. */
        gcmkERR_BREAK(gckOS_StartThread(
            Interrupt->os,
            _MainInterruptHandler,
            Interrupt,
            &Interrupt->handler
            ));
#endif

        /* Success. */
        return gcvSTATUS_OK;
    }
    while (gcvFALSE);

    /* Roll back. */
    if (Interrupt->fifoValid != gcvNULL)
    {
        gcmkCHECK_STATUS(gckOS_DestroySemaphore(
            Interrupt->os, Interrupt->fifoValid
            ));

        Interrupt->fifoValid = gcvNULL;
    }

    /* Return the status. */
    return status;
}

static gceSTATUS
_StopInterruptHandler(
    gckVGINTERRUPT Interrupt
    )
{
    gceSTATUS status;

    do
    {
        /* Does the thread exist? */
        if (Interrupt->handler == gcvNULL)
        {
            /* The semaphore must be NULL as well. */
            gcmkASSERT(Interrupt->fifoValid == gcvNULL);

            /* Success. */
            status = gcvSTATUS_OK;
            break;
        }

        /* The semaphore must exist as well. */
        gcmkASSERT(Interrupt->fifoValid != gcvNULL);

        /* Set the termination request. */
        Interrupt->terminate = gcvTRUE;

        /* Unlock the thread. */
        gcmkERR_BREAK(gckOS_IncrementSemaphore(
            Interrupt->os, Interrupt->fifoValid
            ));

        /* Wait until the thread quits. */
        gcmkERR_BREAK(gckOS_StopThread(
            Interrupt->os,
            Interrupt->handler
            ));

        /* Destroy the semaphore. */
        gcmkERR_BREAK(gckOS_DestroySemaphore(
            Interrupt->os, Interrupt->fifoValid
            ));

        /* Reset handles. */
        Interrupt->handler   = gcvNULL;
        Interrupt->fifoValid = gcvNULL;
    }
    while (gcvFALSE);

    /* Return the status. */
    return status;
}


/******************************************************************************\
***************************** Interrupt Object API *****************************
\******************************************************************************/

/*******************************************************************************
**
**  gckVGINTERRUPT_Construct
**
**  Construct an interrupt object.
**
**  INPUT:
**
**      Kernel
**          Pointer to the gckVGKERNEL object.
**
**  OUTPUT:
**
**      Interrupt
**          Pointer to the new gckVGINTERRUPT object.
*/

gceSTATUS
gckVGINTERRUPT_Construct(
    IN gckVGKERNEL Kernel,
    OUT gckVGINTERRUPT * Interrupt
    )
{
    gceSTATUS status;
    gckVGINTERRUPT interrupt = gcvNULL;

    gcmkHEADER_ARG("Kernel=0x%x Interrupt=0x%x", Kernel, Interrupt);

    /* Verify argeuments. */
    gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
    gcmkVERIFY_ARGUMENT(Interrupt != gcvNULL);

    do
    {
        /* Allocate the gckVGINTERRUPT structure. */
        gcmkERR_BREAK(gckOS_Allocate(
            Kernel->os,
            gcmSIZEOF(struct _gckVGINTERRUPT),
            (gctPOINTER *) &interrupt
            ));

        /* Reset the object data. */
        gcmkVERIFY_OK(gckOS_ZeroMemory(
            interrupt, gcmSIZEOF(struct _gckVGINTERRUPT)
            ));

        /* Initialize the object. */
        interrupt->object.type = gcvOBJ_INTERRUPT;

        /* Initialize the object pointers. */
        interrupt->kernel = Kernel;
        interrupt->os     = Kernel->os;

        /* Initialize the current FIFO position. */
        interrupt->head = (gctUINT8)~0;
        interrupt->tail = (gctUINT8)~0;

        /* Start the thread. */
        gcmkERR_BREAK(_StartInterruptHandler(interrupt));

        /* Return interrupt object. */
        *Interrupt = interrupt;

        gcmkFOOTER_ARG("*Interrup=0x%x", *Interrupt);
        /* Success. */
        return gcvSTATUS_OK;
    }
    while (gcvFALSE);

    /* Roll back. */
    if (interrupt != gcvNULL)
    {
        /* Free the gckVGINTERRUPT structure. */
        gcmkVERIFY_OK(gckOS_Free(interrupt->os, interrupt));
    }

    gcmkFOOTER();

    /* Return the status. */
    return status;
}


/*******************************************************************************
**
**  gckVGINTERRUPT_Destroy
**
**  Destroy an interrupt object.
**
**  INPUT:
**
**      Interrupt
**          Pointer to the gckVGINTERRUPT object to destroy.
**
**  OUTPUT:
**
**      Nothing.
*/

gceSTATUS
gckVGINTERRUPT_Destroy(
    IN gckVGINTERRUPT Interrupt
    )
{
    gceSTATUS status;

    gcmkHEADER_ARG("Interrupt=0x%x", Interrupt);

    /* Verify the arguments. */
    gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT);

    do
    {
        /* Stop the interrupt thread. */
        gcmkERR_BREAK(_StopInterruptHandler(Interrupt));

        /* Mark the object as unknown. */
        Interrupt->object.type = gcvOBJ_UNKNOWN;

        /* Free the gckVGINTERRUPT structure. */
        gcmkERR_BREAK(gckOS_Free(Interrupt->os, Interrupt));
    }
    while (gcvFALSE);

    gcmkFOOTER();

    /* Return the status. */
    return status;
}


/*******************************************************************************
**
**  gckVGINTERRUPT_DumpState
**
**  Print the current state of the interrupt manager.
**
**  INPUT:
**
**      Interrupt
**          Pointer to a gckVGINTERRUPT object.
**
**  OUTPUT:
**
**      Nothing.
*/

#if gcvDEBUG
gceSTATUS
gckVGINTERRUPT_DumpState(
    IN gckVGINTERRUPT Interrupt
    )
{
    gcmkHEADER_ARG("Interrupt=0x%x", Interrupt);
    /* Verify the arguments. */
    gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT);

    /* Print the header. */
    gcmkTRACE_ZONE(
        gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
        "%s: INTERRUPT OBJECT STATUS\n",
        __FUNCTION__
        );

    /* Print statistics. */
#if gcmENABLE_INTERRUPT_STATISTICS
    gcmkTRACE_ZONE(
        gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
        "  Maximum number of FIFO items accumulated at a single time: %d\n",
        Interrupt->maxFifoItems
        );

    gcmkTRACE_ZONE(
        gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
        "  Interrupt FIFO overflow happened times: %d\n",
        Interrupt->fifoOverflow
        );

    gcmkTRACE_ZONE(
        gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
        "  Maximum number of interrupts simultaneously generated: %d\n",
        Interrupt->maxSimultaneous
        );

    gcmkTRACE_ZONE(
        gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
        "  Number of times when there were multiple interrupts generated: %d\n",
        Interrupt->multipleCount
        );
#endif

    gcmkTRACE_ZONE(
        gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
        "  The current number of entries in the FIFO: %d\n",
        Interrupt->fifoItems
        );

    /* Print the FIFO contents. */
    if (Interrupt->fifoItems != 0)
    {
        gctUINT8 index;
        gctUINT8 last;

        gcmkTRACE_ZONE(
            gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
            "  FIFO current contents:\n"
            );

        /* Get the current pointers. */
        index = Interrupt->tail;
        last  = Interrupt->head;

        while (index != last)
        {
            /* Advance to the next entry. */
            index += 1;

            gcmkTRACE_ZONE(
                gcvLEVEL_VERBOSE, gcvZONE_COMMAND,
                "    %d: 0x%08X\n",
                index, Interrupt->fifo[index]
                );
        }
    }

    gcmkFOOTER_NO();
    /* Success. */
    return gcvSTATUS_OK;
}
#endif


/*******************************************************************************
**
**  gckVGINTERRUPT_Enable
**
**  Enable the specified interrupt.
**
**  INPUT:
**
**      Interrupt
**          Pointer to a gckVGINTERRUPT object.
**
**      Id
**          Pointer to the variable that holds the interrupt number to be
**          registered in range 0..31.
**          If the value is less then 0, gckVGINTERRUPT_Enable will attempt
**          to find an unused interrupt. If such interrupt is found, the number
**          will be assigned to the variable if the functuion call succeedes.
**
**      Handler
**          Pointer to the handler to register for the interrupt.
**
**  OUTPUT:
**
**      Nothing.
*/

gceSTATUS
gckVGINTERRUPT_Enable(
    IN gckVGINTERRUPT Interrupt,
    IN OUT gctINT32_PTR Id,
    IN gctINTERRUPT_HANDLER Handler
    )
{
    gceSTATUS status;
    gctINT32 i;

    gcmkHEADER_ARG("Interrupt=0x%x Id=0x%x Handler=0x%x", Interrupt, Id, Handler);

    /* Verify the arguments. */
    gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT);
    gcmkVERIFY_ARGUMENT(Id != gcvNULL);
    gcmkVERIFY_ARGUMENT(Handler != gcvNULL);

    do
    {
        /* See if we need to allocate an ID. */
        if (*Id < 0)
        {
            /* Find the first unused interrupt handler. */
            for (i = 0; i < gcmCOUNTOF(Interrupt->handlers); ++i)
            {
                if (Interrupt->handlers[i] == gcvNULL)
                {
                    break;
                }
            }

            /* No unused innterrupts? */
            if (i == gcmCOUNTOF(Interrupt->handlers))
            {
                status = gcvSTATUS_OUT_OF_RESOURCES;
                break;
            }

            /* Update the interrupt ID. */
            *Id = i;
        }

        /* Make sure the ID is in range. */
        else if (*Id >= gcmCOUNTOF(Interrupt->handlers))
        {
            status = gcvSTATUS_INVALID_ARGUMENT;
            break;
        }

        /* Set interrupt handler. */
        Interrupt->handlers[*Id] = Handler;

        /* Success. */
        status = gcvSTATUS_OK;
    }
    while (gcvFALSE);

    gcmkFOOTER();
    /* Return the status. */
    return status;
}


/*******************************************************************************
**
**  gckVGINTERRUPT_Disable
**
**  Disable the specified interrupt.
**
**  INPUT:
**
**      Interrupt
**          Pointer to a gckVGINTERRUPT object.
**
**      Id
**          Interrupt number to be disabled in range 0..31.
**
**  OUTPUT:
**
**      Nothing.
*/

gceSTATUS
gckVGINTERRUPT_Disable(
    IN gckVGINTERRUPT Interrupt,
    IN gctINT32 Id
    )
{
    gcmkHEADER_ARG("Interrupt=0x%x Id=0x%x", Interrupt, Id);
    /* Verify the arguments. */
    gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT);
    gcmkVERIFY_ARGUMENT((Id >= 0) && (Id < gcmCOUNTOF(Interrupt->handlers)));

    /* Reset interrupt handler. */
    Interrupt->handlers[Id] = gcvNULL;

    gcmkFOOTER_NO();
    /* Success. */
    return gcvSTATUS_OK;
}


/*******************************************************************************
**
**  gckVGINTERRUPT_Enque
**
**  Read the interrupt status register and put the value in the interrupt FIFO.
**
**  INPUT:
**
**      Interrupt
**          Pointer to a gckVGINTERRUPT object.
**
**  OUTPUT:
**
**      Nothing.
*/

#ifndef __QNXNTO__
gceSTATUS
gckVGINTERRUPT_Enque(
    IN gckVGINTERRUPT Interrupt
    )
#else
gceSTATUS
gckVGINTERRUPT_Enque(
    IN gckVGINTERRUPT Interrupt,
    OUT gckOS *Os,
    OUT gctSEMAPHORE *Semaphore
    )
#endif
{
    gceSTATUS status;
    gctUINT32 triggered;

    gcmkHEADER_ARG("Interrupt=0x%x", Interrupt);

    /* Verify the arguments. */
    gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT);

#ifdef __QNXNTO__
    *Os = gcvNULL;
    *Semaphore = gcvNULL;
#endif

    do
    {
        /* Read interrupt status register. */
        gcmkERR_BREAK(gckVGHARDWARE_ReadInterrupt(
            Interrupt->kernel->hardware, &triggered
            ));

        /* No interrupts to process? */
        if (triggered == 0)
        {
            status = gcvSTATUS_NOT_OUR_INTERRUPT;
            break;
        }

        /* FIFO overflow? */
        if (Interrupt->fifoItems == gcmCOUNTOF(Interrupt->fifo))
        {
#if gcmENABLE_INTERRUPT_STATISTICS
            Interrupt->fifoOverflow += 1;
#endif

            /* OR the interrupt with the last value in the FIFO. */
            Interrupt->fifo[Interrupt->head] |= triggered;

            /* Success (kind of). */
            status = gcvSTATUS_OK;
        }
        else
        {
            /* Advance to the next entry. */
            Interrupt->head      += 1;
            Interrupt->fifoItems += 1;

#if gcmENABLE_INTERRUPT_STATISTICS
            if (Interrupt->fifoItems > Interrupt->maxFifoItems)
            {
                Interrupt->maxFifoItems = Interrupt->fifoItems;
            }
#endif

            /* Set the new value. */
            Interrupt->fifo[Interrupt->head] = triggered;

#ifndef __QNXNTO__
            /* Increment the FIFO semaphore. */
            gcmkERR_BREAK(gckOS_IncrementSemaphore(
                Interrupt->os, Interrupt->fifoValid
                ));
#else
            *Os = Interrupt->os;
            *Semaphore = Interrupt->fifoValid;
#endif

            /* Windows kills our threads prematurely when the application
               exists. Verify here that the thread is still alive. */
            status = gckOS_VerifyThread(Interrupt->os, Interrupt->handler);

            /* Has the thread been prematurely terminated? */
            if (status != gcvSTATUS_OK)
            {
                /* Process all accumulated interrupts. */
                while (Interrupt->head != Interrupt->tail)
                {
#if gcmENABLE_INTERRUPT_STATISTICS
                    /* Process the interrupt. */
                    _ProcessInterrupt(Interrupt, gcvNULL);
#else
                    /* Process the interrupt. */
                    _ProcessInterrupt(Interrupt);
#endif
                }

                /* Set success. */
                status = gcvSTATUS_OK;
            }
        }
    }
    while (gcvFALSE);

    gcmkFOOTER();
    /* Return status. */
    return status;
}

#endif /* gcdENABLE_VG */
