/*******************************************************************************
 
   Copyright (c) Hilscher GmbH. All Rights Reserved.
 
 *******************************************************************************
 
   Filename:
    $Id: netX50_gpio_irq.c 329 2010-04-20 15:21:05Z MichaelT $
   Last Modification:
    $Author: MichaelT $
    $Date: 2010-04-20 17:21:05 +0200 (Di, 20 Apr 2010) $
    $Revision: 329 $

   Description:
      netX50 GPIO Interrupt loss workaround
       
   Changes:
 
     Version    Date        Author   Description
     ---------------------------------------------------------------------------
      1         15.04.2010  MT       created
        
*******************************************************************************/

#include "netX50regs.h"
#include "GPIO_Struct.h"
#include "AtomicsGnu.h"
#include "netx50_gpio_irq.h"
#include <string.h> /* Needed for NULL definition */

static NETX_GPIO_REGISTERS_T*         s_ptGpioBase      = (NETX_GPIO_REGISTERS_T*)NETX_GPIO_AREA;

/* Callbacks for additional GPIO workarounds hooks (e.g. IOLink) */
PFN_GPIO_PRECLEAR_NOTIFICATION g_pfnConfirmGpioPreClear     = NULL;
void*                          g_pvConfirmGpioPreClearUser  = NULL;
PFN_GPIO_PRECLEAR_NOTIFICATION g_pfnConfirmGpioPostClear    = NULL;
void*                          g_pvConfirmGpioPostClearUser = NULL;

#define PEEK(a)   (*(volatile unsigned int*)(a))
#define POKE(a,b) (*(volatile unsigned int*)(a) = b)

#define MSK_GPIO_COUNTER0_2  (MSK_NETX_CNTR_IRQ_RAW_cnt0 | \
                              MSK_NETX_CNTR_IRQ_RAW_cnt1 | \
                              MSK_NETX_CNTR_IRQ_RAW_cnt2)

#define MSK_GPIO_COUNTER3_4  (MSK_NETX_CNTR_IRQ_RAW_cnt3 | \
                              MSK_NETX_CNTR_IRQ_RAW_cnt4)

/*****************************************************************************/
/*! Replaces the write to NETX_CNTR_IRQ_RAW and NETX_GPIO_IRQ_RAW for irq
*   confirmation. This must be called by everyone who wants to confirm an
*   interrupt generated by the GPIO module
*   \param ulConfirm  NETX_CNTR_IRQ_RAW / NETX_GPIO_IRQ_RAW depending on the IRQ to confirm
*   \param ulMask     Interrupt mask to confirm                              */
/*****************************************************************************/
void netX50ConfirmGpioIrq(unsigned long ulConfirm, unsigned long ulMask)
{ 
  unsigned int  uiIrq;
  int           iIdx;

  /* Make these variables static so they can be placed in fast memory by linker.
     Sharing / Race conditions are no problem as we are only using them inside 
     IRQ locked code */
  static unsigned long aulTimerValues[5] = {0};
  static unsigned long aulTimerCtrl[5]   = {0};
  static unsigned long ulSystimeNs;
  unsigned long        ulSoftIntMask     = 0;
  unsigned long        ulTimerIrqMask;

  lock_irq_save(uiIrq);

  /* Call any other gpio workaround hooks, before we are confirming IRQ on hardware */
  if(NULL != g_pfnConfirmGpioPreClear)
    g_pfnConfirmGpioPreClear(g_pvConfirmGpioPreClearUser);

  if(NETX_CNTR_IRQ_RAW == ulConfirm)
  {
    /* On Timer confirmation we need to clear software interrupts on VIC */
    unsigned long ulSoftIntConfirm = (((ulMask & MSK_GPIO_COUNTER0_2) >> SRT_NETX_CNTR_IRQ_RAW_cnt0) << SRT_NETX_VIC_SWI_timer0) |
                                     (((ulMask & MSK_GPIO_COUNTER3_4) >> SRT_NETX_CNTR_IRQ_RAW_cnt3) << SRT_NETX_VIC_SWI_timer3) |
                                     (((ulMask & MSK_NETX_CNTR_IRQ_RAW_sys_time) >> SRT_NETX_CNTR_IRQ_RAW_sys_time) << SRT_NETX_VIC_SWI_systime_ns);
                                       
    POKE(NETX_VIC_SWI_CLR, ulSoftIntConfirm);
  }
  
  /* Store all current timers */
  for(iIdx = 0; iIdx < sizeof(s_ptGpioBase->ulCounter) / sizeof(s_ptGpioBase->ulCounter[0]); ++iIdx)
  {
    aulTimerValues[iIdx] = s_ptGpioBase->ulCounter[iIdx];
    aulTimerCtrl[iIdx]   = s_ptGpioBase->ulCounterCtrl[iIdx];
  }
  
  /* Save Systime value */
  PEEK(NETX_SYS_TIME_S);
  ulSystimeNs = PEEK(NETX_SYS_TIME_NS); 

  POKE(ulConfirm, ulMask);

  ulTimerIrqMask = s_ptGpioBase->ulCntIrqMaskSet;

  /* Verify system agains gpio */
  if(ulTimerIrqMask & MSK_NETX_CNTR_IRQ_MSK_sys_time)
  {
    unsigned long ulNewSystimeNs;
    unsigned long ulSystimeCmp   = s_ptGpioBase->ulSystimeCmp;
    
    PEEK(NETX_SYS_TIME_S);
    ulNewSystimeNs = PEEK(NETX_SYS_TIME_NS);
    
    if (ulNewSystimeNs > ulSystimeNs)
    {
      if ((ulSystimeNs < ulSystimeCmp) && (ulNewSystimeNs > ulSystimeCmp))
        ulSoftIntMask |= MSK_NETX_VIC_SWI_systime_ns;
    }
    else
    {
      if ((ulSystimeNs < ulSystimeCmp) || (ulNewSystimeNs > ulSystimeCmp))
        ulSoftIntMask |= MSK_NETX_VIC_SWI_systime_ns;
    }
  }

  /* Check for timers that may have caused an IRQ during our IRQ acknowledge */
  for(iIdx = 0; iIdx < sizeof(s_ptGpioBase->ulCounter) / sizeof(s_ptGpioBase->ulCounter[0]); ++iIdx)
  {
    /* Timer0-2 are continues from Bit1-3, 3-4 are on Bits 29/30 */
    unsigned long ulTempIrqMsk = (iIdx < 3) ? 
                                 MSK_NETX_VIC_SWI_timer0 << iIdx : 
                                 MSK_NETX_VIC_SWI_timer3 << (iIdx - 3);

    /* Only process timers that are supposed to generate an IRQ and were 
       running before*/
    if( (ulTimerIrqMask & (1 << iIdx))                         &&
        (aulTimerCtrl[iIdx] & MSK_NETX_GPIO_CNTR0_CTRL_irq_en) &&
        (aulTimerCtrl[iIdx] & MSK_NETX_GPIO_CNTR0_CTRL_run) )
    {
      if(0 == (aulTimerCtrl[iIdx] & MSK_NETX_GPIO_CNTR0_CTRL_once))
      {
        /* This is a cyclic timer, so we just need to check if the new value is 
           smaller than the old one and generate an IRQ if neccessary */
        if(s_ptGpioBase->ulCounter[iIdx] < aulTimerValues[iIdx])
          ulSoftIntMask |= ulTempIrqMsk;

      } else
      {
        /* Single shot timer */
        if(0 == (s_ptGpioBase->ulCounterCtrl[iIdx] & MSK_NETX_GPIO_CNTR0_CTRL_run))
        {
          /* Timer was running at start of Confirmation and has stopped now,
             so this timer has expired */
          ulSoftIntMask |= ulTempIrqMsk;
        }
      }
    }
  }

  /* Generate software interrupt for all counters that expired. This may result 
     in "normal" AND software IRQ being set. Make sure the interrupt handlers clears both.
     Software interrupts will be cleared when calling netX50HandleTimerIrq */
  POKE(NETX_VIC_SWI, ulSoftIntMask); 

  /* Call any other gpio workaround hooks, after we have confirmed IRQ on hardware */
  if(NULL != g_pfnConfirmGpioPostClear)
    g_pfnConfirmGpioPostClear(g_pvConfirmGpioPostClearUser);

  lock_irq_restore(uiIrq);
}
