/******************************************************************************
  * \attention
  *
  * <h2><center>&copy; COPYRIGHT 2016 STMicroelectronics</center></h2>
  *
  * Licensed under ST MYLIBERTY SOFTWARE LICENSE AGREEMENT (the "License");
  * You may not use this file except in compliance with the License.
  * You may obtain a copy of the License at:
  *
  *        www.st.com/myliberty
  *
  * Unless required by applicable law or agreed to in writing, software 
  * distributed under the License is distributed on an "AS IS" BASIS, 
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
  * AND SPECIFICALLY DISCLAIMING THE IMPLIED WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
******************************************************************************/


/*
 *      PROJECT:   ST25R3911 firmware
 *      Revision:
 *      LANGUAGE:  ISO C99
 */

/*! \file
 *
 *  \author Ulrich Herrmann
 *
 *  \brief ST25R3911 Interrupt handling
 *
 */

/*
******************************************************************************
* INCLUDES
******************************************************************************
*/
#include "st25r3911_interrupt.h"
#include "st25r3911_com.h"
#include "st25r3911.h"
#include "st_errno.h"
#include "utils.h"

/*
******************************************************************************
* GLOBAL DEFINES
******************************************************************************
*/

/*! Length of the interrupt registers       */
#define ST25R3911_INT_REGS_LEN          ( (ST25R3911_REG_IRQ_ERROR_WUP - ST25R3911_REG_IRQ_MAIN) + 1U )

/*
 ******************************************************************************
 * LOCAL DATA TYPES
 ******************************************************************************
 */

/*! Holds current and previous interrupt callback pointer as well as current Interrupt status and mask */
typedef struct
{
    void      (*prevCallback)(void); /*!< call back function for 3911 interrupt               */
    void      (*callback)(void);     /*!< call back function for 3911 interrupt               */
    uint32_t  status;                /*!< latest interrupt status                             */
    uint32_t  mask;                  /*!< Interrupt mask. Negative mask = ST25R3911 mask regs */
}t_st25r3911Interrupt;

/*
******************************************************************************
* GLOBAL VARIABLES
******************************************************************************
*/

static volatile t_st25r3911Interrupt st25r3911interrupt; /*!< Instance of ST25R3911 interrupt */

/*
******************************************************************************
* GLOBAL FUNCTIONS
******************************************************************************
*/
void st25r3911InitInterrupts( void )
{
    platformIrqST25R3911PinInitialize();
    platformIrqST25R3911SetCallback( st25r3911Isr );
    
    st25r3911interrupt.callback     = NULL;
    st25r3911interrupt.prevCallback = NULL;
    st25r3911interrupt.status       = ST25R3911_IRQ_MASK_NONE;
    st25r3911interrupt.mask         = ST25R3911_IRQ_MASK_NONE;
    
    /* Initialize LEDs if existing and defined */
    platformLedsInitialize();

#ifdef PLATFORM_LED_RX_PIN
    platformLedOff( PLATFORM_LED_RX_PORT, PLATFORM_LED_RX_PIN );
#endif /* PLATFORM_LED_RX_PIN */

#ifdef PLATFORM_LED_FIELD_PIN
    platformLedOff( PLATFORM_LED_FIELD_PORT, PLATFORM_LED_FIELD_PIN );
#endif /* PLATFORM_LED_FIELD_PIN */
}

void st25r3911Isr( void )
{
    st25r3911CheckForReceivedInterrupts();
    
    if (NULL != st25r3911interrupt.callback)
    {
        st25r3911interrupt.callback();
    }
}

void st25r3911CheckForReceivedInterrupts( void )
{
    uint8_t  iregs[ST25R3911_INT_REGS_LEN];
    uint32_t irqStatus; 

    irqStatus = ST25R3911_IRQ_MASK_NONE;
    ST_MEMSET( iregs, (int32_t)(ST25R3911_IRQ_MASK_ALL & 0xFFU), ST25R3911_INT_REGS_LEN );  /* MISRA 10.3 */
        
    /* In case the IRQ is Edge (not Level) triggered read IRQs until done */
    while( platformGpioIsHigh( ST25R391X_INT_PORT, ST25R391X_INT_PIN ) )
    {
        st25r3911ReadMultipleRegisters(ST25R3911_REG_IRQ_MAIN, iregs, sizeof(iregs));
       
#ifdef PLATFORM_LED_FIELD_PIN         
        if ((iregs[0] & ST25R3911_IRQ_MASK_TXE) != 0U)
        {
            platformLedOn( PLATFORM_LED_FIELD_PORT, PLATFORM_LED_FIELD_PIN );
        }
#endif /* PLATFORM_LED_FIELD_PIN */
       
#ifdef PLATFORM_LED_RX_PIN
        if ((iregs[0] & ST25R3911_IRQ_MASK_RXS) != 0)
        {
            platformLedOn( PLATFORM_LED_RX_PORT, PLATFORM_LED_RX_PIN );
        }
        if (((iregs[0] & ST25R3911_IRQ_MASK_RXE) != 0) || ((iregs[1] & (ST25R3911_IRQ_MASK_NRE >> 8)) != 0)) /* In rare cases there is rxs but not rxe, then we have nre */
        {
            platformLedOff( PLATFORM_LED_RX_PORT, PLATFORM_LED_RX_PIN );
        }
#endif /* PLATFORM_LED_RX_PIN */
       
        irqStatus |= (uint32_t)iregs[0];
        irqStatus |= (uint32_t)iregs[1]<<8;
        irqStatus |= (uint32_t)iregs[2]<<16;
    }
    
    /* Forward all interrupts, even masked ones to application. */
    platformProtectST25R391xIrqStatus();
    st25r3911interrupt.status |= irqStatus;
    platformUnprotectST25R391xIrqStatus();
}


void st25r3911ModifyInterrupts(uint32_t clr_mask, uint32_t set_mask)
{
    uint8_t i;
    uint32_t old_mask;
    uint32_t new_mask;

    old_mask = st25r3911interrupt.mask;
    new_mask = (~old_mask & set_mask) | (old_mask & clr_mask);
    st25r3911interrupt.mask &= ~clr_mask;
    st25r3911interrupt.mask |= set_mask;
    for (i=0; i<3U ; i++)
    { 
        if (((new_mask >> (i*8U)) & 0xffU) == 0U) {
            continue;
        }
        st25r3911WriteRegister((ST25R3911_REG_IRQ_MASK_MAIN + i), (uint8_t)((st25r3911interrupt.mask>>(i*8U))&0xffU));
    }
    return;
}


uint32_t st25r3911WaitForInterruptsTimed(uint32_t mask, uint16_t tmo)
{
    uint32_t tmr;
    uint32_t status;
   
    tmr = platformTimerCreate(tmo);
    do 
    {
        status = (st25r3911interrupt.status & mask);
    } while( ( !platformTimerIsExpired( tmr ) || (tmo == 0U)) && (status == 0U) );

    status = st25r3911interrupt.status & mask;
    
    platformProtectST25R391xIrqStatus();
    st25r3911interrupt.status &= ~status;
    platformUnprotectST25R391xIrqStatus();
    
    return status;
}

uint32_t st25r3911GetInterrupt(uint32_t mask)
{
    uint32_t irqs;

    irqs = (st25r3911interrupt.status & mask);
    if (irqs != ST25R3911_IRQ_MASK_NONE)
    {
        platformProtectST25R391xIrqStatus();
        st25r3911interrupt.status &= ~irqs;
        platformUnprotectST25R391xIrqStatus();
    }
    return irqs;
}

void st25r3911EnableInterrupts(uint32_t mask)
{
    st25r3911ModifyInterrupts(mask,0);
}

void st25r3911DisableInterrupts(uint32_t mask)
{
    st25r3911ModifyInterrupts(0,mask);
}

void st25r3911ClearInterrupts( void )
{
    uint8_t iregs[3];

    st25r3911ReadMultipleRegisters(ST25R3911_REG_IRQ_MAIN, iregs, 3);

    platformProtectST25R391xIrqStatus();
    st25r3911interrupt.status = 0;
    platformUnprotectST25R391xIrqStatus();
    return;
}

void st25r3911IRQCallbackSet( void (*cb)(void) )
{
    st25r3911interrupt.prevCallback = st25r3911interrupt.callback;
    st25r3911interrupt.callback     = cb;
}

void st25r3911IRQCallbackRestore( void )
{
    st25r3911interrupt.callback     = st25r3911interrupt.prevCallback;
    st25r3911interrupt.prevCallback = NULL;
}