diff options
Diffstat (limited to 'drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c')
-rw-r--r-- | drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c | 854 |
1 files changed, 854 insertions, 0 deletions
diff --git a/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c b/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c new file mode 100644 index 000000000000..cbc921a713b0 --- /dev/null +++ b/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_interrupt_vg.c @@ -0,0 +1,854 @@ +/**************************************************************************** +* +* Copyright (C) 2005 - 2011 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; + + /* Success. */ + return gcvSTATUS_OK; + } + while (gcvFALSE); + + /* Roll back. */ + if (interrupt != gcvNULL) + { + /* Free the gckVGINTERRUPT structure. */ + gcmkVERIFY_OK(gckOS_Free(interrupt->os, interrupt)); + } + + /* 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. +*/ + +gceSTATUS +gckVGINTERRUPT_Enque( + IN gckVGINTERRUPT Interrupt + ) +{ + gceSTATUS status; + gctUINT32 triggered; + + gcmkHEADER_ARG("Interrupt=0x%x", Interrupt); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Interrupt, gcvOBJ_INTERRUPT); + + 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; + + /* Increment the FIFO semaphore. */ + gcmkERR_BREAK(gckOS_IncrementSemaphore( + Interrupt->os, Interrupt->fifoValid + )); + + /* 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 */ |