/*! *********************************************************************************
* Copyright (c) 2015, Freescale Semiconductor, Inc.
* Copyright 2018 NXP
* All rights reserved.
*
* \file
*
* SPDX-License-Identifier: BSD-3-Clause
********************************************************************************** */

/*! *********************************************************************************
*************************************************************************************
* Include
*************************************************************************************
********************************************************************************** */
#include "EmbeddedTypes.h"
#include "Phy.h"
#include "PhyInterface.h"
#include "PhyPacket.h"
#include "FunctionLib.h"
#include "PhySec.h"

#include "fsl_os_abstraction.h"
#include "fsl_device_registers.h"

#include "nxp2p4_xcvr.h"

#include "fsl_ltc.h"

#if defined(FFU_PHY_ONLY_OVER_IMU)
#include "os_if.h"
#endif


/*! *********************************************************************************
*************************************************************************************
* Private macros
*************************************************************************************
********************************************************************************** */
#if defined(FFU_PHY_ONLY_OVER_IMU)
#define gPhyIrqPriority_c     (1 << 5) //#define CPU_IRQ_PRIORITY_1  (1<<5)
#define FFU_ZIGBEE_INT_IRQn   (13)
#endif

#ifndef gPhyIrqPriority_c
#define gPhyIrqPriority_c (0x80)
#endif
#if defined(KW45B41Z83_NBU_SERIES)
    #define gPhyIrqNo_d ((IRQn_Type)ZIGBEE_INT_IRQn)
#elif defined(RW610N_BT_CM3_SERIES)
    #define gPhyIrqNo_d ((IRQn_Type)FFU_ZIGBEE_INT_IRQn)
#elif defined(K32W1480_SERIES)
    #define gPhyIrqNo_d ((IRQn_Type)53)
#endif

#define PHY_TEN_SYMBOLS_US 160
#define PHY_SYMBOLS_US 16
#define PHY_IE_MAX_SIZE 16

#define LQI_REG_MAX_VAL ((uint8_t)127)    /* Maximum value of LQI from the register*/
#define LQI_MAX_VAL     ((uint8_t)0xFF)   /* Maximum value of LQI*/
#define CLIP_LQI_VAL    ((int16_t)-20)    /* WSW-21635: Clip 15.4 LQI value to 0xFF for above -20dBm */

/*! *********************************************************************************
*************************************************************************************
* Private type definitions
*************************************************************************************
********************************************************************************** */
enum
{
    phyFcfFrameBeacon      = 0 << 0,
    phyFcfFrameData        = 1 << 0,
    phyFcfFrameAck         = 2 << 0,
    phyFcfFrameMacCmd      = 3 << 0,
    phyFcfFrameTypeMask    = 7 << 0,
    phyFcfSecurityEnabled  = 1 << 3,
    phyFcfFramePending     = 1 << 4,
    phyFcfAckRequest       = 1 << 5,
    phyFcfPanidCompression = 1 << 6,
    phyFcfSeqNbCompression = 1 << 8,
    phyFcfIePresent        = 1 << 9,
    phyFcfDstAddrNone      = 0 << 10,
    phyFcfDstAddrShort     = 2 << 10,
    phyFcfDstAddrExt       = 3 << 10,
    phyFcfDstAddrMask      = 3 << 10,
    phyFcfFrameVersion2006 = 1 << 12,
    phyFcfFrameVersion2015 = 2 << 12,
    phyFcfFrameVersionMask = 3 << 12,
    phyFcfSrcAddrNone      = 0 << 14,
    phyFcfSrcAddrShort     = 2 << 14,
    phyFcfSrcAddrExt       = 3 << 14,
    phyFcfSrcAddrMask      = 3 << 14,
    phyAshFmCntSupression  = 1 << 5,
    phyAshKeyIdMode0       = 0 << 3,
    phyAshKeyIdMode1       = 1 << 3,
    phyAshKeyIdMode2       = 2 << 3,
    phyAshKeyIdMode3       = 3 << 3,
    phyAshKeyIdModeMask    = 3 << 3,
    phyAshSecLevelMask     = 7 << 0,
    phyAshSecLevel0        = 0 << 0,
    phyAshSecLevel1        = 1 << 0,
    phyAshSecLevel2        = 2 << 0,
    phyAshSecLevel3        = 3 << 0,
    phyAshSecLevel4        = 4 << 0,
    phyAshSecLevel5        = 5 << 0,
    phyAshSecLevel6        = 6 << 0,
    phyAshSecLevel7        = 7 << 0,
};

enum
{
    PHY_IEEE802154_ACK_LENGTH        = 5,  /* Size of a Imm ACK */
    PHY_IE_HEADER_SIZE               = 2,  /* Size of IE header in bytes */
    PHY_CSL_IE_SIZE                  = 4,  /* Size of CSL IE content in bytes */
    PHY_CSL_TOTAL_SIZE               = 6,  /* Total size of CSL IE in bytes */
    PHY_ACK_IE_MAX_SIZE              = 16, /* Max length for header IE in ACK */
    PHY_SFD_SIZE_BYTES               = 1,  /* Size of SFD in bytes */
    PHY_LEN_SIZE_BYTES               = 1,  /* Size of LEN field in bytes */
    PHY_PREAMBLE_SIZE_BYTES          = 4,  /* Size of Preamble in bytes */
    PHY_ASH_SEC_CTRL_SIZE            = 1,  /* Size of ASH security control field in bytes */
    PHY_ASH_FRAME_CNT_SIZE           = 4,  /* Size of ASH frame counter field in bytes */ 
    PHY_ASH_TOTAL_SIZE               = 6   /* Total size of the ASH using Key ID mode 1  */ 
};

/*! Structure used to keep IE data for a specific peer */
typedef struct phyIeData_tag
{
    uint8_t         ieData[PHY_IE_MAX_SIZE];
    uint8_t         extAddr[8];
    uint16_t        ieDataSize;
    uint16_t        shortAddr;
} phyIeData_t;
/*! *********************************************************************************
*************************************************************************************
* Private memory declarations
*************************************************************************************
********************************************************************************** */
extern Phy_PhyLocalStruct_t     phyLocal;
static volatile phyRxParams_t * mpRxParams        = NULL;
static const uint8_t            mPhyInstance      = 0;
uint8_t                         mPhyLastRxLQI     = 0;
int8_t                          mPhyLastRxRSSI    = 0;
uint8_t                         mPhyIrqDisableCnt = 1;
bool_t                          mPhyForceFP       = FALSE;

/* Set to true to signal we need to send ack as a TX seq because of mode 7&8 HW bug */
static bool_t sPhyManualSwEhAck = false;

static uint32_t sPhyCslPeriod;
static uint32_t sPhyCslSampleTimeUs;
static uint8_t sAckIeDataAndHdr[PHY_CSL_IE_SIZE + PHY_IE_HEADER_SIZE] = {0x04,0x0d,0x00,0x00,0x00,0x00};

static uint8_t *pRxBuffer = (uint8_t*)(TX_PACKET_RAM_BASE+0x80U);
static uint8_t *pTxBuffer = (uint8_t*)(TX_PACKET_RAM_BASE);
static uint8_t  pktRamBuffer[127];

static phyIeData_t phyIeDataTable[gPhyIeDataTableSize] = {0};

/* used by EFP feature */
#if gPhyEfpEnabled
#define gPhyHwIndQueueSize_d (128)
#define mPhyGetPBTransferThreshold(len) ((len) - 2)
extern uint8_t gPhyHwIndQueueSize_c;
extern uint16_t gPhySwTable[gPhyHwIndQueueSize_d];
#endif

/*! *********************************************************************************
*************************************************************************************
* Private prototypes
*************************************************************************************
********************************************************************************** */
static void PhyIsrSeqCleanup(void);
static void PhyIsrTimeoutCleanup(void);
static void Phy_GetRxParams(void);
static uint8_t Phy_LqiConvert(uint8_t hwLqi);

static bool_t PHY_CheckSwEnhAck(uint16_t fcf, uint32_t length);
static bool_t PHY_CheckIfCslIsNeeded();
/* For V18 DS5 compiler, this function is defined but not called, so remove when macro is defined */
#if !defined(FFU_PHY_ONLY_OVER_IMU)
#if (KW45_A0_SUPPORT == 1) // A1 specific defines
static void PHY_StartTxSwEnhAckSeq();
#endif
#endif
static uint32_t PHY_CalculateTxAckMhrTstampSym(uint32_t sfdDectecTimestampSym, uint32_t rxPktLen);
static uint16_t PHY_TransformArrayToUint16(uint8_t *pArray);
static void PHY_TransformUint16ToArray(uint8_t *pArray, uint16_t value);
static void PHY_GenerateSwEnhAck(uint32_t rxLength);
static void PHY_SetDataAckFp(uint16_t fcf);
static uint32_t PHY_GetKeySourceLength(uint8_t keyIdMode);
static uint8_t PHY_GetMicLeght(uint8_t securityLevel);
static phyIeData_t* PHY_GetAckIeData(uint16_t shortAddr, uint8_t *extAddr, uint32_t addrLen);

/*! *********************************************************************************
*************************************************************************************
* Public memory declarations
*************************************************************************************
********************************************************************************** */

/*! *********************************************************************************
*************************************************************************************
* Public functions
*************************************************************************************
********************************************************************************** */

/*! *********************************************************************************
* \brief  Disable the 802.15.4 radio IRQ
*
********************************************************************************** */
void ProtectFromXcvrInterrupt(void)
{
    OSA_InterruptDisable();

    if (mPhyIrqDisableCnt == 0)
    {
        ZLL->PHY_CTRL |= ZLL_PHY_CTRL_TRCV_MSK_MASK;
    }

    mPhyIrqDisableCnt++;

    OSA_InterruptEnable();
}

/*! *********************************************************************************
* \brief  Enable the 802.15.4 radio IRQ
*
********************************************************************************** */
void UnprotectFromXcvrInterrupt(void)
{
    OSA_InterruptDisable();

    if (mPhyIrqDisableCnt)
    {
        mPhyIrqDisableCnt--;

        if (mPhyIrqDisableCnt == 0)
        {
            ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_TRCV_MSK_MASK;
        }
    }

    OSA_InterruptEnable();
}

/*! *********************************************************************************
* \brief  Sets the location of the Rx parameters
*
* \param[in]  pRxParam pointer to Rx parameters
*
********************************************************************************** */
void PhyIsrPassRxParams(volatile phyRxParams_t *pRxParam)
{
    mpRxParams = pRxParam;
}

/*! *********************************************************************************
* \brief  Configure IE data that will be put in an Enhanced ack
*
* \param[in]  pIeData pointer to IE data
* \param[in]  ieDataLen IE data len, if > 0 add entry, if = 0 remove entry
* \param[in]  shortAddr short address of the peer device
* \param[in]  extAddr extended address of the peer device
*
********************************************************************************** */
void PHY_ConfigureAckIeData(uint8_t * pIeData, uint32_t ieDataLen, uint16_t shortAddr, uint8_t *extAddr)
{

    /* Returns entry corresponding to short address or the first empty one */
    phyIeData_t *pIeDataEntry = PHY_GetAckIeData(shortAddr, NULL, sizeof(uint16_t));

    /* Add case */
    if ((ieDataLen > 0) && (NULL != pIeData))
    {
        if (NULL != pIeDataEntry)
        {
            /* Add/Update data */
            memcpy(pIeDataEntry->ieData, pIeData, ieDataLen);
            memcpy(pIeDataEntry->extAddr, extAddr, sizeof(uint64_t));
            pIeDataEntry->ieDataSize = ieDataLen;
            pIeDataEntry->shortAddr = shortAddr;
        }
    }
    else
    {
        /* Remove case */
        if ((NULL != pIeDataEntry) && (pIeDataEntry->ieDataSize != 0))
        {
            memset(pIeDataEntry, 0, sizeof(phyIeData_t));
        }
    }
}

/*! *********************************************************************************
* \brief  Enable CSL IE inclusion in Enhanced ACK 
*
********************************************************************************** */
void PHY_EnableCsl(uint32_t cslPeriod)
{
   sPhyCslPeriod = cslPeriod;
   if (sPhyCslPeriod > 0)
   {
       memcpy(sAckIeDataAndHdr + sizeof(HdrIe_t) + sizeof(uint16_t), (uint8_t*)&sPhyCslPeriod, sizeof(uint16_t));
   }
}
/*! *********************************************************************************
* \brief  Set Csl sample time in us
*
********************************************************************************** */
void PHY_SetCslSampleTime(uint32_t cslSampleTimeUs)
{
    sPhyCslSampleTimeUs = cslSampleTimeUs;
}

/*! *********************************************************************************
* \brief  Copy data to/from packet ram in a safe mannner
*
********************************************************************************** */
void PHY_MemCpy(volatile uint8_t *dst, uint8_t *src, uint32_t len)
{
    for (uint32_t i = 0; i < len ; i++)
    {
        dst[i] = src[i];
    }
}

/*! *********************************************************************************
* \brief  Calculate CSL phase
*
********************************************************************************** */
uint16_t PHY_GetCslPhase(uint32_t macHdrTimestampSym)
{
    uint32_t cslPeriodInUs = sPhyCslPeriod * PHY_TEN_SYMBOLS_US;
    /* convert the sfd timestamp to us */
    macHdrTimestampSym *= PHY_SYMBOLS_US;
    uint32_t diff = (cslPeriodInUs - (macHdrTimestampSym % cslPeriodInUs) + (sPhyCslSampleTimeUs % cslPeriodInUs)) % cslPeriodInUs;
    return (uint16_t)(diff / PHY_TEN_SYMBOLS_US + 1);
}

/*! *********************************************************************************
* \brief  Returns entry corresponding to short address or the first empty one
*
* \param[in]  shortAddr short address of the peer device
* \param[in]  extAddr extended address of the peer device
* \param[in]  addrLen identifies the type of address used short = 2, extended = 8
*
********************************************************************************** */
static phyIeData_t* PHY_GetAckIeData(uint16_t shortAddr, uint8_t *extAddr, uint32_t addrLen)
{
    phyIeData_t * pFoundEntry = NULL;
    /* MCRED-77: Added the if statement to avoid a corner case where addrLen is 0
     * TODO: Need to come up with a correct fix */
    if(addrLen > 0U)
    {
        assert ((addrLen == 2) || (addrLen == 8));
    
        for(uint32_t i = 0; i < gPhyIeDataTableSize; i++)
        {
            if (phyIeDataTable[i].ieDataSize)
            {
                if (addrLen == sizeof(uint16_t))
                {
                    if (shortAddr == phyIeDataTable[i].shortAddr)
                    {
                        pFoundEntry = &phyIeDataTable[i];
                        break;
                    }
                }
                else
                {
                    if (memcmp(extAddr, phyIeDataTable[i].extAddr, addrLen))
                    {
                        pFoundEntry = &phyIeDataTable[i];
                        break;
                    }
                }
            }
            else if (NULL == pFoundEntry)
            {
                pFoundEntry = &phyIeDataTable[i];
            }
        }
    }   
    
    return pFoundEntry;
}

/*! *********************************************************************************
* \brief  Returns the key source length 
*
********************************************************************************** */
static uint32_t PHY_GetKeySourceLength(uint8_t keyIdMode)
{
    uint8_t len = 0;

    switch (keyIdMode)
    {
    case phyAshKeyIdMode0:
        len = 0;
        break;

    case phyAshKeyIdMode1:
        len = 1;
        break;

    case phyAshKeyIdMode2:
        len = 5;
        break;

    case phyAshKeyIdMode3:
        len = 9;
        break;
    }
    return len;
}

/*! *********************************************************************************
* \brief  Returns the MIC length based on security level
*
********************************************************************************** */
static uint8_t PHY_GetMicLeght(uint8_t securityLevel)
{
    uint8_t micSize = 0;

    switch (securityLevel)
    {
    case phyAshSecLevel0:
    case phyAshSecLevel4:
        micSize = 0;
        break;

    case phyAshSecLevel1:
    case phyAshSecLevel5:
        micSize = 4;
        break;

    case phyAshSecLevel2:
    case phyAshSecLevel6:
        micSize = 8;
        break;

    case phyAshSecLevel3:
    case phyAshSecLevel7:
        micSize = 16;
        break;
    }
    return micSize;
}
/*! *********************************************************************************
* \brief  Check if we need to generate an Enahced ACK in response in SW
*
************************************************************************************/
static bool_t PHY_CheckSwEnhAck(uint16_t fcf, uint32_t length)
{
    bool_t bSendSwEnhAck = false;
    if (fcf & phyFcfFrameVersion2015)
    {
        bSendSwEnhAck = true;
        
#if (KW45_A0_SUPPORT == 1) // A1 specific defines        
        if (((fcf & phyFcfDstAddrMask) == phyFcfDstAddrExt) && 
            ((fcf & phyFcfSrcAddrMask) == phyFcfSrcAddrExt))
        {
            bSendSwEnhAck = true;

            /* Schedule TX enhaced ACK sequnce */
            uint32_t startOfAckTstamp = ZLL->TIMESTAMP >> ZLL_TIMESTAMP_TIMESTAMP_SHIFT;
            startOfAckTstamp += (length + 1) * gPhySymbolsPerOctet_c + gPhyTurnaroundTime_c;
            
             /* Compensate XCVR Tx warmup time */
            startOfAckTstamp -= gPhyTxWuTimeSym;
            PhyTimeSetEventTrigger(startOfAckTstamp);
            /* Setting this to true will start tx sequence after the current RX seq finishes */
            sPhyManualSwEhAck = true;
        }
#endif
    }
    return bSendSwEnhAck;
}

/*! *********************************************************************************
* \brief  Generate a Sw Enahced ACK
*
************************************************************************************/
static void PHY_GenerateSwEnhAck(uint32_t rxLength)
{
    uint16_t rxFcf = PHY_TransformArrayToUint16(pRxBuffer);
    uint32_t varHdrLen = PhyPacket_GetHdrLength(pRxBuffer);
    uint16_t txFcf = phyFcfFrameAck | phyFcfFrameVersion2015 | phyFcfSrcAddrNone;
    uint8_t *pSrcAddr = NULL;
    uint32_t srcAddrLen = 0;
    uint32_t txIndex = 0;
    uint32_t copyIndex = 0;
    uint32_t mhrLen = 0;
    uint32_t micSize = 0;
    uint8_t  pktLen = 0;
    bool_t bCslNeeded = false;
    bool_t bIeNeeded = false;
    uint16_t checksum = 0;
    uint16_t panId = ZLL->MACSHORTADDRS0 & ZLL_MACSHORTADDRS0_MACPANID0_MASK;
    uint16_t shortSrcAddr = 0;
    phyIeData_t *pAckIeData = NULL;
    
    /* Len field */
    txIndex += sizeof(uint8_t);

    /* This is not working, we need to rely on sw to calculate FP bit */
    if (ZLL->ENHACK_CTRL0 & ZLL_ENHACK_CTRL0_HW_FRAME_PENDING_MASK)
    {
        txFcf |= phyFcfFramePending;
    }

    if (rxFcf & phyFcfPanidCompression)
    {
        txFcf |= phyFcfPanidCompression;
    }

    /* Destination address mode of ACK packet */
    if ((rxFcf & phyFcfSrcAddrMask) == phyFcfSrcAddrExt)
    {
        txFcf |= phyFcfDstAddrExt;
        srcAddrLen = sizeof(uint64_t);
        pSrcAddr = pRxBuffer + (varHdrLen - srcAddrLen);
    }
    else if ((rxFcf & phyFcfSrcAddrMask) == phyFcfSrcAddrShort)
    {
        txFcf |= phyFcfDstAddrShort;
        srcAddrLen = sizeof(uint16_t);
        pSrcAddr = pRxBuffer + (varHdrLen - srcAddrLen);
        shortSrcAddr = PHY_TransformArrayToUint16(pSrcAddr);
    }
    else
    {
        txFcf |= phyFcfDstAddrNone;
    }

    /* Calculate checksum based on source address and panid and search SAP table for a match */
    checksum = PhyGetChecksum(pSrcAddr, (srcAddrLen == 2) ? mShortAddr_d : mExtAddr_d, panId);
    if (PhyGetIndexOf(checksum) != -1)
    {
        /* Set FP bit if we have a checksum match in SAP table */
        txFcf |= phyFcfFramePending;
    }

    /* Check if we need to add CSL information element to the packet */
    if (PHY_CheckIfCslIsNeeded())
    {
        txFcf |= phyFcfIePresent;
        bCslNeeded = true;
    }
    /* Check if we need to add generic information elements to the packet */
    pAckIeData = PHY_GetAckIeData(shortSrcAddr, pSrcAddr, srcAddrLen);
    if ((NULL != pAckIeData) && (pAckIeData->ieDataSize > 0))
    {
        txFcf |= phyFcfIePresent;
        bIeNeeded = true;
    }

    /* Use security in ACK frame only if the frame has security and contains CSL or IE or frame payload. 
       In other cases, the security is optional so we choose to not use it as it doesn't provide any benefit */
    if (rxFcf & phyFcfSecurityEnabled)
    {
        if (bCslNeeded || bIeNeeded)
        {
            txFcf |= phyFcfSecurityEnabled;
        }
    }

    PHY_TransformUint16ToArray(pktRamBuffer + txIndex, txFcf);
    txIndex += sizeof(txFcf);
    
    /* Set seq number */
    pktRamBuffer[txIndex] = pRxBuffer[sizeof(rxFcf)];
    txIndex += sizeof(uint8_t);
    
    /* Set destination panid if needed */
    if ((txFcf & phyFcfPanidCompression) == 0)
    {
        PHY_TransformUint16ToArray((pktRamBuffer + txIndex), panId);
        txIndex += sizeof(panId);
    }

    /* Set destination address if needed */
    if (pSrcAddr)
    {
        memcpy(pktRamBuffer + txIndex, pSrcAddr, srcAddrLen);
        txIndex += srcAddrLen;
    }

    /* Save an index of the MAC header witout ASH or IE, this header is safe to transmit */
    copyIndex = txIndex;

    if (txFcf & phyFcfSecurityEnabled)
    { 
        /* Copy security control field and keyId from RX packet */
        pktRamBuffer[txIndex] = pRxBuffer[varHdrLen];
        micSize = PHY_GetMicLeght(pktRamBuffer[txIndex] & phyAshSecLevelMask);
            
        /* Get the key index and source if present */
        uint32_t keySourceLength = PHY_GetKeySourceLength(pktRamBuffer[txIndex] & phyAshKeyIdModeMask);

        if (pktRamBuffer[txIndex] & phyAshFmCntSupression)
        {
            txIndex += PHY_ASH_SEC_CTRL_SIZE;
            memcpy(pktRamBuffer + txIndex, pRxBuffer + varHdrLen + PHY_ASH_SEC_CTRL_SIZE, keySourceLength);
        }
        else
        {
            txIndex += PHY_ASH_SEC_CTRL_SIZE + PHY_ASH_FRAME_CNT_SIZE;
            memcpy(pktRamBuffer + txIndex, pRxBuffer + varHdrLen + PHY_ASH_SEC_CTRL_SIZE + PHY_ASH_FRAME_CNT_SIZE, keySourceLength);
        }
        txIndex += keySourceLength;
    }
    
    /* TxIndex at this point records the len of the MHR */
    mhrLen = txIndex;

    if (bCslNeeded)
    {
        /* Calculate the timestamp of the tx ack mac hdr from the rx sfd timestamp */
        uint32_t sfdDetectTstampSym = ZLL->TIMESTAMP >> ZLL_TIMESTAMP_TIMESTAMP_SHIFT;
        
        uint16_t cslPhase = PHY_GetCslPhase(PHY_CalculateTxAckMhrTstampSym(sfdDetectTstampSym, rxLength));
        memcpy(sAckIeDataAndHdr + sizeof(HdrIe_t), (uint8_t*)&cslPhase, sizeof(uint16_t));
        memcpy(pktRamBuffer + txIndex, sAckIeDataAndHdr, PHY_CSL_TOTAL_SIZE);
        txIndex += PHY_CSL_TOTAL_SIZE;
    }

    /* Copy generic IE data to ACK packet */
    if (bIeNeeded)
    {
        memcpy(pktRamBuffer + txIndex, pAckIeData->ieData, pAckIeData->ieDataSize);
        txIndex += pAckIeData->ieDataSize;
    }

    /* Set size of frame in index 0 of the tx buffer - from txIndex substract 1 byte 
       for the len and add the FCS 2 bytes */
    pktLen = txIndex - 1 + sizeof(uint16_t) + micSize;
    pktRamBuffer[0] = pktLen;

    /* Load into packet RAM MAC header until copyIndex as it safe to transmit */
    PHY_MemCpy(pTxBuffer, pktRamBuffer, copyIndex);

    /* Save at the end of the buffer the rssi and lqi values */
    pktRamBuffer[pktLen]     = (ZLL->LQI_AND_RSSI & ZLL_LQI_AND_RSSI_RSSI_MASK) >> ZLL_LQI_AND_RSSI_RSSI_SHIFT;
    pktRamBuffer[pktLen + 1] = (ZLL->LQI_AND_RSSI & ZLL_LQI_AND_RSSI_LQI_VALUE_MASK) >> ZLL_LQI_AND_RSSI_LQI_VALUE_SHIFT;
    pktRamBuffer[pktLen + 1] =  Phy_LqiConvert(pktRamBuffer[pktLen + 1]);
 
    /* Set ENH_ACK_LEN register based on calculated len from txIndex */
    ZLL->ENHACK_CTRL0 |= ZLL_ENHACK_CTRL0_SW_LEN_RDY(1) |
                         ZLL_ENHACK_CTRL0_SW_MHR_LENGTH(mhrLen) |
                         ZLL_ENHACK_CTRL0_SW_HIE_RDY(1);

    if (PhySec_IsEnabled() && (txFcf & phyFcfSecurityEnabled))
    {
        PhyPacket_SetFrameCounter(pktRamBuffer + 1, ++sFrameCounter);
        PhySec_Encrypt(pktRamBuffer + 1, pktRamBuffer[0]);
        phyLocal.rxParams.ackedWithSecEnhAck = true;
    }

    /* Copy the rest of the data into packet RAM starting from the where we left off at the MAC hdr */
    PHY_MemCpy(pTxBuffer + copyIndex, pktRamBuffer + copyIndex, pktLen - copyIndex);

}

/*! *********************************************************************************
* \brief  Check if we need to set the frame pending bit of an ACK generated in response
          to a data packet(poll packet is handleld by HW)
*
************************************************************************************/
static void PHY_SetDataAckFp(uint16_t fcf)
{

    uint16_t srcAddrMode = fcf & phyFcfSrcAddrMask;
    uint16_t dstAddrMode = fcf & phyFcfDstAddrMask;
    uint8_t rxIndex = 0;
    uint16_t checksum = 0;
    uint16_t panId;

    if ((fcf & phyFcfFrameData) && 
        ((srcAddrMode == phyFcfSrcAddrShort) || (srcAddrMode == phyFcfSrcAddrExt)))
    {
        /* Store dst PAN Id */
        rxIndex += sizeof(fcf) + !(fcf & phyFcfSeqNbCompression);
        panId = PHY_TransformArrayToUint16(pRxBuffer + rxIndex);
        rxIndex += sizeof(panId);

        /* Skip over dst addr fields */
        if( dstAddrMode == phyFcfDstAddrShort )
        {
            rxIndex += sizeof(uint16_t);
        }
        else if( dstAddrMode == phyFcfDstAddrExt )
        {
            rxIndex += sizeof(uint64_t);
        }
        else if( dstAddrMode != mNoAddr_d )
        {
            mPhyForceFP = FALSE;
            return;
        }

        /* Store src PanId if present */
        if ((fcf & phyFcfPanidCompression) == 0)
        {
            panId  = PHY_TransformArrayToUint16(pRxBuffer + rxIndex);
            rxIndex += sizeof(panId);
        }

        /* Get FP state */
        checksum = PhyGetChecksum(pRxBuffer + rxIndex, srcAddrMode >> 14, panId);
        if (PhyGetIndexOf(checksum) != -1)
        {
            PhyPpSetFpManually(TRUE);
        }
        else
        {
            PhyPpSetFpManually(FALSE);
        }
    }
}
/* For V18 DS5 compiler, this function is defined but not called, so remove when macro is defined */
#if !defined(FFU_PHY_ONLY_OVER_IMU)
#if (KW45_A0_SUPPORT == 1) // A1 specific defines
/*! *********************************************************************************
* \brief  Check if we need to generate an Enahced ACK in response in SW
*
************************************************************************************/
static void PHY_StartTxSwEnhAckSeq()
{
    /* Set IDLE sequence */
    ZLL->PHY_CTRL |= gIdle_c;
    
    /* No CCA, unslotted operation */
    ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_CCABFRTX_MASK;
    ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_SLOTTED_MASK;

    /* Set TX Seq with no ack requested */
    ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_RXACKRQD_MASK;
    ZLL->PHY_CTRL |= gTX_c;

    /* Unmask SEQ interrupt */
    ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_SEQMSK_MASK;
}
#endif
#endif
/*! *********************************************************************************
* \brief  Check if we need to include CSL IE in the Sw Enahced ACK
*
************************************************************************************/
static bool_t PHY_CheckIfCslIsNeeded()
{
    bool_t bCslIsNeed = false;
    ///TODO: Add also address match
    if (sPhyCslPeriod >  0)
    {
        bCslIsNeed = true;
    }
    
    return bCslIsNeed;
}

/*! *********************************************************************************
* \brief  Adjust timestamp so that it reflects start of TX eAck MHR starting from received SFD
*
************************************************************************************/
static uint32_t PHY_CalculateTxAckMhrTstampSym(uint32_t sfdDectecTimestampSym, uint32_t rxPktLen)
{
    /* Len(RX len + SFD + LEN field) of RX packet in symbols */
    uint32_t timestamp = sfdDectecTimestampSym + ((rxPktLen + 2) * gPhySymbolsPerOctet_c);

    /* Add turnaround time + TX Preamble, SFD and LEn */
    timestamp += gPhyTurnaroundTime_c + gPhySHRDuration_c + gPhyPHRDuration_c;

    return timestamp;

}
/*!*************************************************************************************************
\brief  Converts an big endian array to a 16 bit numeric value

 ***************************************************************************************************/
static uint16_t PHY_TransformArrayToUint16(uint8_t *pArray)
{
    union uuint16_t
    {
        uint16_t    u16;     /*!< 16bit variable */
        uint8_t     u8[2];   /*!< 8bit array */
    };
/* For V18 DS5 compiler, variable need be given initial value */
    union uuint16_t out ={0};

    out.u8[0] = *pArray++;
    out.u8[1] = *pArray;

    return out.u16;
}

/*!*************************************************************************************************
\brief  Converts a 16 bit numeric value to array.

 ***************************************************************************************************/
static void PHY_TransformUint16ToArray(uint8_t *pArray, uint16_t value)
{
    *pArray++ = (uint8_t)(value);
    *pArray   = (uint8_t)(value >> 8);
}

/*! *********************************************************************************
* \brief  Clear and mask PHY IRQ, set sequence to Idle
*
********************************************************************************** */
static void PhyIsrSeqCleanup(void)
{
    uint32_t irqStatus;

    /* Set the PHY sequencer back to IDLE */
    ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_XCVSEQ_MASK;
    /* Mask SEQ, RX, TX and CCA interrupts */
    ZLL->PHY_CTRL |= ZLL_PHY_CTRL_CCAMSK_MASK |
                     ZLL_PHY_CTRL_RXMSK_MASK  |
                     ZLL_PHY_CTRL_TXMSK_MASK  |
                     ZLL_PHY_CTRL_SEQMSK_MASK;

    while (ZLL->SEQ_STATE & ZLL_SEQ_STATE_SEQ_STATE_MASK)
    {
    }

    irqStatus = ZLL->IRQSTS;
    /* Mask TMR3 interrupt */
    irqStatus |= ZLL_IRQSTS_TMR3MSK_MASK;
    /* Clear transceiver interrupts except TMRxIRQ */
    irqStatus &= ~( ZLL_IRQSTS_TMR1IRQ_MASK |
                    ZLL_IRQSTS_TMR2IRQ_MASK |
                    ZLL_IRQSTS_TMR3IRQ_MASK |
                    ZLL_IRQSTS_TMR4IRQ_MASK );
    ZLL->IRQSTS = irqStatus;
#if 1 //gPhyEfpEnabled
    ZLL->SAM_TABLE &= ~(ZLL_SAM_TABLE_ACK_FRM_PND_CTRL_MASK);
#endif
}

/*! *********************************************************************************
* \brief  Clear and mask PHY IRQ, disable timeout, set sequence to Idle
*
********************************************************************************** */
static void PhyIsrTimeoutCleanup(void)
{
    uint32_t irqStatus;

    /* Set the PHY sequencer back to IDLE and disable TMR3 comparator and timeout */
    ZLL->PHY_CTRL &= ~(ZLL_PHY_CTRL_TMR3CMP_EN_MASK |
                       ZLL_PHY_CTRL_TC3TMOUT_MASK   |
                       ZLL_PHY_CTRL_XCVSEQ_MASK);
    /* Mask SEQ, RX, TX and CCA interrupts */
    ZLL->PHY_CTRL |= ZLL_PHY_CTRL_CCAMSK_MASK |
                     ZLL_PHY_CTRL_RXMSK_MASK  |
                     ZLL_PHY_CTRL_TXMSK_MASK  |
                     ZLL_PHY_CTRL_SEQMSK_MASK;

    while (ZLL->SEQ_STATE & ZLL_SEQ_STATE_SEQ_STATE_MASK)
    {
    }

    irqStatus = ZLL->IRQSTS;
    /* Mask TMR3 interrupt */
    irqStatus |= ZLL_IRQSTS_TMR3MSK_MASK;
    /* Clear transceiver interrupts except TMR1IRQ and TMR4IRQ. */
    irqStatus &= ~(ZLL_IRQSTS_TMR1IRQ_MASK | ZLL_IRQSTS_TMR4IRQ_MASK);
    ZLL->IRQSTS = irqStatus;
#if 1 //gPhyEfpEnabled
    ZLL->SAM_TABLE &= ~(ZLL_SAM_TABLE_ACK_FRM_PND_CTRL_MASK);
#endif
}

/*! *********************************************************************************
* \brief  Scales energy level to 0-255
*
* \param[in]  energyLevel  the energy level reported by HW
*
* \return  uint8_t  the energy level scaled in 0x00-0xFF
*
********************************************************************************** */
uint8_t Phy_GetEnergyLevel(uint8_t energyLevel) /* [dbm] */
{
    int32_t temp = (int8_t)energyLevel;

    if (temp <= -80)
    {
        temp = 0x00;
    }
    else if (temp >= -3)
    {
        temp = 0xFF;
    }
    else
    {
        /* Convert energy level from dbm into a 0x00-0xFF value */
        temp = (17 * temp) / 5 + 272;
    }

    return (uint8_t)temp;
}

/*! *********************************************************************************
* \brief  This function returns the LQI for the las received packet
*
* \return  uint8_t  LQI value
*
********************************************************************************** */
uint8_t PhyGetLastRxLqiValue(void)
{
    return mPhyLastRxLQI;
}

/*! *********************************************************************************
* \brief  This function returns the RSSI for the las received packet
*
* \return  uint8_t  RSSI value
*
********************************************************************************** */
uint8_t PhyGetLastRxRssiValue(void)
{
    /*RSSI*/
    /*
    **       LQI
    **RSSI = ---  - LQI_COMP
    **       2.25
    */
    uint8_t LQI_COMP = (ZLL->CCA_LQI_CTRL & ZLL_CCA_LQI_CTRL_LQI_OFFSET_COMP_MASK) >> ZLL_CCA_LQI_CTRL_LQI_OFFSET_COMP_SHIFT;
    int32_t lqi_to_rssi = ((mPhyLastRxRSSI * 456u) >> 10u) + 5 - LQI_COMP;

    return (uint8_t)lqi_to_rssi;
}

/*! *********************************************************************************
* \brief  This function converts the LQI reported by the PHY into an signed RSSI value
*
* \param[in]  LQI  the LQI reported by the PHY
*
* \return  the RSSI value in dbm
*
********************************************************************************** */
int8_t PhyConvertLQIToRSSI(uint8_t lqi)
{
    int32_t rssi = (36 * lqi - 9836) / 109;

    return (int8_t)rssi;
}

/*! *********************************************************************************
* \brief  Determines the state of the FP bit for an outgoing ACK frame.
*         If the source address is not prezent in the neighbor table, FP will be set.
*
* \param[in]  pPsdu  pointer to the PSDU
*
********************************************************************************** */
#if gPhyEfpEnabled
static void PreProcessPacket (uint8_t *pPsdu)
{
    uint8_t  dstAddrMode;
    uint8_t  srcAddrMode;
    uint16_t PanId;
    uint16_t hash = 0;
    uint32_t i = 0;
    bool_t   panIdCompression;

    /* Get addressing information */
    dstAddrMode = (pPsdu[mFrameCtrlHi_d] & mDstAddrModeMask_d) >> mDstAddrModeShift_d;
    srcAddrMode = (pPsdu[mFrameCtrlHi_d] & mSrcAddrModeMask_d) >> mSrcAddrModeShift_d;
    panIdCompression = pPsdu[mFrameCtrlLo_d] & mPanIdCompression_d;

    if((srcAddrMode != mShortAddr_d) && (srcAddrMode != mExtAddr_d ))
    {
        return;
    }

    /* Store dst PAN Id */
    pPsdu += mAddressingFields_d;
    PanId  = *pPsdu++;
    PanId |= (*pPsdu++) << 8;

    /* Skip over dst addr fields */
    if( dstAddrMode == mShortAddr_d )
    {
        pPsdu += sizeof(uint16_t);
    }
    else if( dstAddrMode == mExtAddr_d )
    {
        pPsdu += sizeof(uint64_t);
    }
    else if( dstAddrMode != mNoAddr_d )
    {
        mPhyForceFP = FALSE;
        return;
    }

    /* Store src PanId if present */
    if( !panIdCompression )
    {
        PanId  = *pPsdu++;
        PanId |= (*pPsdu++) << 8;
    }

    /* Get FP state */
    hash = PhyGetChecksum(pPsdu, srcAddrMode, PanId);

    for ( i = 0; i < gPhyIndirectQueueSize_c; i++)
    {
        if (gPhySwTable[i] == hash)
        {
            break;
        }
    }

    if(i < gPhyIndirectQueueSize_c)
    {
        PhyPpSetFpManually(TRUE);
    }
    else
    {
        PhyPpSetFpManually(FALSE);
    }
}
#endif

/*! *********************************************************************************
* \brief  PHY ISR
*
********************************************************************************** */
/* No vector table in RAM for now in OpenThread build
   So we need to implement directly the handler defined in the startup code */
#if defined(CPU_KW45B41Z83AFPA_NBU)
void ZIGBEE_INT_IRQHandler(void)
{
    /* wrap 802.15.4 interrupt */
    PHY_InterruptHandler();
}
#elif defined(RW610N_BT_CM3_SERIES)
#if defined(FFU_PHY_ONLY_OVER_IMU)
UINT32 FFU_ZIGBEE_INT_IRQHandler(UINT32 vector, UINT32 data)
#else
void FFU_ZIGBEE_INT_IRQHandler(void)
#endif
{
    /* wrap 802.15.4 interrupt */
    PHY_InterruptHandler();
#if defined(FFU_PHY_ONLY_OVER_IMU)
    return 0;
#endif
}
#elif defined(K32W1480_SERIES)
void RF_802_15_4_IRQHandler(void)
{
    /* wrap 802.15.4 interrupt */
    PHY_InterruptHandler();
}
#endif

void PHY_InterruptHandler(void)
{
    uint8_t xcvseqCopy;
    uint32_t irqStatus;
    uint32_t length;
    uint32_t enhack_ctrl0;
    uint32_t crc_valid;
    static uint8_t filter_fail = 0;
    static uint8_t rx_hdr = 0; /* boolean indicating that the header has been received */
    static uint32_t varHdrLen = 0;
    static uint32_t rx_watermark_lvl = gPhyDefaultRxWatermarkLevel_d;
    static uint16_t rxFcf;

#if defined(RW610N_BT_CM3_SERIES)
#if defined(gPhyUseExternalCoexistence_d) && (gPhyUseExternalCoexistence_d == 1)
    uint32_t seqCtrlStatus;
    seqCtrlStatus = ZLL->SEQ_CTRL_STS;
#endif /* defined(gPhyUseExternalCoexistence_d) && (gPhyUseExternalCoexistence_d == 1) */
#endif /* defined(RW610N_BT_CM3_SERIES) */

    enhack_ctrl0 = ZLL->ENHACK_CTRL0;

    /* Read current XCVRSEQ and interrup status */
    xcvseqCopy  = ZLL->PHY_CTRL & ZLL_PHY_CTRL_XCVSEQ_MASK;
    irqStatus   = ZLL->IRQSTS;
    /* Clear ZLL interrupts. The TX, RX, CCA and TC3 IRQs will be handled on SEQIRQ and must not be cleared. */
    ZLL->IRQSTS = irqStatus & ~(ZLL_IRQSTS_TMR3IRQ_MASK | ZLL_IRQSTS_CCAIRQ_MASK | ZLL_IRQSTS_RXIRQ_MASK | ZLL_IRQSTS_TXIRQ_MASK);

#if !defined(RW610N_BT_CM3_SERIES)
#if defined(gPhyUseExternalCoexistence_d) && (gPhyUseExternalCoexistence_d == 1)
    if(irqStatus & ZLL_IRQSTS_ARB_GRANT_DEASSERTION_IRQ_MASK)
    {
        // TODO: software should probably react to an external RF NOT ALLOWED signal
        // See Jira ticket: KFOURWONE-382
        // Dbg_IO_Set(2, 1);
        // Dbg_IO_Set(2, 0);
    }
#endif
#endif /* !defined(RW610N_BT_CM3_SERIES) */

    if (enhack_ctrl0 & ZLL_ENHACK_CTRL0_EMPTY_ACK_IRQ_MASK) {
      /* This should never happen. If it does it means that there is not enough
         time to construct the EnhAck packet berfore we have to send it. */
      ZLL->ENHACK_CTRL0 = enhack_ctrl0 & ~ZLL_ENHACK_CTRL0_EMPTY_ACK_IRQ_MASK;

      /* When using debug IO signal with a small spike that EMPTY_ACK irq has triggered */
      // Dbg_IO_Set(2, 1);
      // Dbg_IO_Set(2, 0);
    }

    /* WAKE IRQ */
    if (irqStatus & ZLL_IRQSTS_WAKE_IRQ_MASK)
    {
#if 0
        // TODO: Update DSM handling using RFMC (RSIM not existing)
        uint32_t timeAdjust = RSIM->MAN_WAKE;
        /* Adjust the 802.15.4 EVENT_TMR */
        timeAdjust = (uint64_t)(timeAdjust - RSIM->MAN_SLEEP) * 1000000U / 32768; /* [us] */
        ZLL->EVENT_TMR = ((timeAdjust >> 0x4) << ZLL_EVENT_TMR_EVENT_TMR_SHIFT)      | /* [symbols]: divide by 16 */
                         ((timeAdjust & 0x0F) << ZLL_EVENT_TMR_EVENT_TMR_FRAC_SHIFT) | /* [us]: modulo 16 */
                         ZLL_EVENT_TMR_EVENT_TMR_ADD_MASK;
        ZLL->DSM_CTRL = 0;
#endif
    }

    /* Flter Fail IRQ */
    if (irqStatus & ZLL_IRQSTS_FILTERFAIL_IRQ_MASK)
    {
#if gMWS_UseCoexistence_d
        MWS_CoexistenceReleaseAccess();
#endif
        if (!(ZLL->PHY_CTRL & ZLL_PHY_CTRL_FILTERFAIL_MSK_MASK))
        {
            Radio_Phy_PlmeFilterFailRx(mPhyInstance);
        }
        filter_fail = 1;
#if 1 //gPhyEfpEnabled
        ZLL->RX_WTR_MARK = gPhyDefaultRxWatermarkLevel_d;
        ZLL->SAM_TABLE &= ~(ZLL_SAM_TABLE_ACK_FRM_PND_CTRL_MASK);
#endif
    }
    /* Rx Watermark IRQ */
    else
    {
        if ((!(ZLL->PHY_CTRL & ZLL_PHY_CTRL_RX_WMRK_MSK_MASK)) && (irqStatus & ZLL_IRQSTS_RXWTRMRKIRQ_MASK) && (xcvseqCopy == gRX_c))
        {
            length = (irqStatus & ZLL_IRQSTS_RX_FRAME_LENGTH_MASK) >> ZLL_IRQSTS_RX_FRAME_LENGTH_SHIFT;
            phyLocal.rxParams.pRxData->msgData.dataInd.psduLength = length;
            Radio_Phy_PlmeRxWatermark(mPhyInstance, length);

            if (!rx_hdr)
            {
                rx_hdr = 1;
                
                rxFcf = PHY_TransformArrayToUint16(pRxBuffer);
                varHdrLen = PhyPacket_GetHdrLength(pRxBuffer) - sizeof(rxFcf);
                
                /* advance watermak to the end of the ASH */
                ZLL->RX_WTR_MARK = rx_watermark_lvl + varHdrLen + PHY_ASH_TOTAL_SIZE;
            } 
            else 
            {                
                /* Check if we need to send an enhanced ack for this packet, else let entire RX seq finish */
                if (PHY_CheckSwEnhAck(rxFcf, length))
                {
                    PHY_GenerateSwEnhAck(length);
                }
                else
                {
                    /* Check if we need to set FP bit in case of a received data frame (Enhanced Frame Pending)*/
                    PHY_SetDataAckFp(rxFcf);
                }
            }

#if gPhyEfpEnabled
            if(gPhyDefaultRxWatermarkLevel_d == ZLL->RX_WTR_MARK )
            {
                /* Check if this is a standalone RX because we could end up here during a TR sequence also. */
                if( xcvseqCopy == gRX_c )
                {
                    if(!(irqStatus & ZLL_IRQSTS_SEQIRQ_MASK))
                    {
                        ZLL->RX_WTR_MARK = mPhyGetPBTransferThreshold(length);
                    }
                }
            }
            else
            {
                if((length <= 127) && (!(ZLL->IRQSTS & ZLL_IRQSTS_PI_MASK))) /* poll request check */
                {
                    PreProcessPacket((uint8_t *)ZLL->PKT_BUFFER_RX);
                }
                ZLL->RX_WTR_MARK = gPhyDefaultRxWatermarkLevel_d;
            }
#endif
#if gMWS_UseCoexistence_d
            if( (xcvseqCopy == gRX_c) && (gMWS_Success_c != MWS_CoexistenceRequestAccess(gMWS_RxState_c)) )
            {
                PhyAbort();
                Radio_Phy_TimeRxTimeoutIndication(mPhyInstance);
            }
#endif
        }
    }

    /* Timer 1 Compare Match */
    if ((irqStatus & ZLL_IRQSTS_TMR1IRQ_MASK) && (!(irqStatus & ZLL_IRQSTS_TMR1MSK_MASK)))
    {
        PhyTimeDisableWaitTimeout();
        PhyTime_ISR();
    }

    /* Sequencer interrupt, the autosequence has completed */
    if ((!(ZLL->PHY_CTRL & ZLL_PHY_CTRL_SEQMSK_MASK)) && (irqStatus & ZLL_IRQSTS_SEQIRQ_MASK))
    {
        // Dbg_IO_Set(2, 1);

#if gPhyEfpEnabled
        ZLL->RX_WTR_MARK = gPhyDefaultRxWatermarkLevel_d;
#endif
        /* PLL unlock, the autosequence has been aborted due to PLL unlock */
        if (irqStatus & ZLL_IRQSTS_PLL_UNLOCK_IRQ_MASK)
        {
            PhyIsrSeqCleanup();
#if gMWS_UseCoexistence_d
            MWS_CoexistenceReleaseAccess();
#endif
            Radio_Phy_PlmeSyncLossIndication(mPhyInstance);
        }
#if defined(RW610N_BT_CM3_SERIES)
#if defined(gPhyUseExternalCoexistence_d) && (gPhyUseExternalCoexistence_d == 1)
        /* Arbitration Grant Loss, the autosequence has been aborted due to ARB_GRANT_DEASSERTION */
        else if((seqCtrlStatus & ZLL_SEQ_CTRL_STS_ARB_GRANT_DEASSERTION_ABORTED_MASK) ||
                (irqStatus & ZLL_IRQSTS_ARB_GRANT_DEASSERTION_IRQ_MASK))
        {
            PhyIsrSeqCleanup();
            PhyIsrTimeoutCleanup();

            /* Set default Rx watermark level */
            ZLL->RX_WTR_MARK = gPhyDefaultRxWatermarkLevel_d;
            rx_hdr = 0;
#if gMWS_UseCoexistence_d
            MWS_CoexistenceReleaseAccess();
#endif
            // Clear up the RF_NOT_ALLOWED_TX_ABORT/RF_NOT_ALLOWED_RX_ABORT by clearing
            // RF_NOT_ALLOWED_ASSERTED bit in 0xA9066008[4] COEXISTENCE CONTROL (COEX_CTRL) Register
            if(RADIO_CTRL->COEX_CTRL & RADIO_CTRL_COEX_CTRL_RF_NOT_ALLOWED_ASSERTED_MASK)
            {
                RADIO_CTRL->COEX_CTRL |= RADIO_CTRL_COEX_CTRL_RF_NOT_ALLOWED_ASSERTED(1);
            }

            if(irqStatus & ZLL_IRQSTS_ARB_GRANT_DEASSERTION_IRQ_MASK)
            {
                // ARB_GRANT_DEASSERTION interrupt is already cleared, nothing to do!
                // TODO: software should probably react to an external RF NOT ALLOWED signal
                // See Jira ticket: KFOURWONE-382
                // Debug_IO_Set(2, 1);
                // Debug_IO_Set(2, 0);
            }
            Radio_Phy_AbortIndication(mPhyInstance);
        }
        /* COEX Timeout, the autosequence has been aborted due to COEX Timeout */
        else if (ZLL->COEX_CTRL & ZLL_COEX_CTRL_COEX_TIMEOUT_IRQ_MASK)
        {
            PhyIsrSeqCleanup();
            PhyIsrTimeoutCleanup();
#if gMWS_UseCoexistence_d
            MWS_CoexistenceReleaseAccess();
#endif
            // Clear COEX_TIMEOUT_IRQ flag
            ZLL->COEX_CTRL |= ZLL_COEX_CTRL_COEX_TIMEOUT_IRQ(1);

            // Clear up the RF_NOT_ALLOWED_TX_ABORT/RF_NOT_ALLOWED_RX_ABORT by clearing
            // RF_NOT_ALLOWED_ASSERTED bit in 0xA9066008[4] COEXISTENCE CONTROL (COEX_CTRL) Register
            if(RADIO_CTRL->COEX_CTRL & RADIO_CTRL_COEX_CTRL_RF_NOT_ALLOWED_ASSERTED_MASK)
            {
                RADIO_CTRL->COEX_CTRL |= RADIO_CTRL_COEX_CTRL_RF_NOT_ALLOWED_ASSERTED(1);
            }
            Radio_Phy_AbortIndication(mPhyInstance);
        }
#endif /* defined(gPhyUseExternalCoexistence_d) && (gPhyUseExternalCoexistence_d == 1) */
#endif /* defined(RW610N_BT_CM3_SERIES) */
        /* TMR3 timeout, the autosequence has been aborted due to TMR3 timeout */
        else if ((irqStatus & ZLL_IRQSTS_TMR3IRQ_MASK) &&
                 (!(irqStatus & ZLL_IRQSTS_RXIRQ_MASK)) &&
                 (gTX_c != xcvseqCopy))
        {
            PhyIsrTimeoutCleanup();
#if gMWS_UseCoexistence_d
            MWS_CoexistenceReleaseAccess();
#endif
            Radio_Phy_TimeRxTimeoutIndication(mPhyInstance);
        }
        else
        {
            // Dbg_IO_Set(1, 1);

            PhyIsrSeqCleanup();
#if gMWS_UseCoexistence_d
            MWS_CoexistenceReleaseAccess();
#endif
            switch (xcvseqCopy)
            {
            case gTX_c:
                if (sPhyManualSwEhAck)
                {
                    sPhyManualSwEhAck = false;
                }
                else
                {
                    if ((ZLL->PHY_CTRL & ZLL_PHY_CTRL_CCABFRTX_MASK) && (irqStatus & ZLL_IRQSTS_CCA_MASK))
                    {
                        Radio_Phy_PlmeCcaConfirm(gPhyChannelBusy_c, mPhyInstance);
                    }
                    else
                    {
                        phyLocal.rxParams.psduLength = 1;
                        Radio_Phy_PdDataConfirm(mPhyInstance, FALSE);
                    }
                }
                break;

            case gTR_c:
                if ((ZLL->PHY_CTRL & ZLL_PHY_CTRL_CCABFRTX_MASK) && (irqStatus & ZLL_IRQSTS_CCA_MASK))
                {    
                    Radio_Phy_PlmeCcaConfirm(gPhyChannelBusy_c, mPhyInstance);
                }
                else if (irqStatus & ZLL_IRQSTS_RXIRQ_MASK)
                {
                    Phy_GetRxParams();

                    if (!(irqStatus & ZLL_IRQSTS_ENH_PKT_STATUS_MASK))
                    {
                        pRxBuffer[1] = 0;
                        pRxBuffer[0] = phyFcfFrameAck;

                        if (PhyPpIsRxAckDataPending())
                        {
                            pRxBuffer[0] |= phyFcfFramePending;
                        }
                        pRxBuffer[2] = pTxBuffer[3];
                        phyLocal.rxParams.psduLength = 3;
                    }
                    Radio_Phy_PdDataConfirm(mPhyInstance, (irqStatus & ZLL_IRQSTS_RX_FRM_PEND_MASK) > 0);
                }
#if defined(RW610N_BT_CM3_SERIES)
                else
                {
                    // if SEQIRQ is raised but RxIRQ not, then an error happened
                    // Do Cleanup before abort
                    PhyIsrSeqCleanup();
                    PhyIsrTimeoutCleanup();

                    /* Set default Rx watermark level */
                    ZLL->RX_WTR_MARK = gPhyDefaultRxWatermarkLevel_d;
                    rx_hdr = 0;
#if gMWS_UseCoexistence_d
                    MWS_CoexistenceReleaseAccess();
#endif
                    Radio_Phy_AbortIndication(mPhyInstance);
                }
#endif /* defined(RW610N_BT_CM3_SERIES)*/
                break;

            case gRX_c:
                /* Frame pending bit can be set for both data and cmd frame for both FV1 and FV2 */
                if (PhyPpIsTxAckDataPending())
                {
                    phyLocal.flags |= gPhyFlagTxAckFP_c;
                }
                else
                {
                    phyLocal.flags &= ~gPhyFlagTxAckFP_c;
                }
                    
#if gPhyUseNeighborTable_d
                if (irqStatus & ZLL_IRQSTS_PI_MASK)
                {
                    /* Check SAA0 and SAA1 (source address absent) */
                    if(((ZLL->SAM_MATCH & (ZLL_SAM_MATCH_SAA0_ADDR_ABSENT_MASK | ZLL_SAM_MATCH_SAP0_ADDR_PRESENT_MASK)) == ZLL_SAM_MATCH_SAA0_ADDR_ABSENT_MASK) ||
                        ((ZLL->SAM_MATCH & (ZLL_SAM_MATCH_SAA1_ADDR_ABSENT_MASK | ZLL_SAM_MATCH_SAP1_ADDR_PRESENT_MASK)) == ZLL_SAM_MATCH_SAA1_ADDR_ABSENT_MASK) )
                    {
                        mPhyForceFP = TRUE;
                    }
                 }
#endif
               
                /* Clear EnhAck after Rx (and Ack Tx) finishes */
                ZLL->ENHACK_CTRL0 &= ~ZLL_ENHACK_CTRL0_SW_LEN_RDY_MASK;
                ZLL->ENHACK_CTRL0 &= ~ZLL_ENHACK_CTRL0_SW_HIE_RDY_MASK;
                ZLL->ENHACK_CTRL0 |= ZLL_ENHACK_CTRL0_SW_MHR_LENGTH(0);
                
                ZLL->SAM_TABLE &= ~(ZLL_SAM_TABLE_ACK_FRM_PND_CTRL_MASK);

                rx_watermark_lvl = gPhyDefaultRxWatermarkLevel_d;
                ZLL->RX_WTR_MARK = rx_watermark_lvl;
                rx_hdr = 0;

                crc_valid = (ZLL->SEQ_STATE & ZLL_SEQ_STATE_CRCVALID_MASK) >> ZLL_SEQ_STATE_CRCVALID_SHIFT;

                Phy_GetRxParams();

                /* Copy the received packet from packet ram to data indication structure */
                memcpy(phyLocal.rxParams.pRxData->msgData.dataInd.pPsdu, pRxBuffer, 
                       phyLocal.rxParams.pRxData->msgData.dataInd.psduLength);

                if (!filter_fail && crc_valid) 
                {
#if (KW45_A0_SUPPORT == 1) // A1 specific defines
                    if (sPhyManualSwEhAck == true)
                    {
                        PHY_StartTxSwEnhAckSeq();

                    }
#endif
                   
                    Radio_Phy_PdDataIndication(mPhyInstance);
                }
                else 
                {
                    filter_fail = 0;
                }

                break;

            case gCCA_c:
                if (gCcaED_c == ((ZLL->PHY_CTRL & ZLL_PHY_CTRL_CCATYPE_MASK) >> ZLL_PHY_CTRL_CCATYPE_SHIFT))
                {
                    Radio_Phy_PlmeEdConfirm((ZLL->LQI_AND_RSSI & ZLL_LQI_AND_RSSI_CCA1_ED_FNL_MASK) >> ZLL_LQI_AND_RSSI_CCA1_ED_FNL_SHIFT, mPhyInstance );
                    if (phyLocal.edScanDurationSym != 0)
                    {
                        /* Restart the energy scan here when scan duration is different from 0 */
                        phyStatus_t status = PhyPlmeCcaEdRequest(gPhyEnergyDetectMode_c, gPhyContCcaDisabled);                        
                        if (gPhySuccess_c != status)
                        {
                            phyLocal.edScanDurationSym = 0;
                            /* Don't disable timeout timer, wait for it to expire */
                        }
                    }
                }
                else /* CCA */
                {
                    if (irqStatus & ZLL_IRQSTS_CCA_MASK)
                    {
                        Radio_Phy_PlmeCcaConfirm(gPhyChannelBusy_c, mPhyInstance);
                    }
                    else
                    {
                        Radio_Phy_PlmeCcaConfirm(gPhyChannelIdle_c, mPhyInstance);
                    }
                }
                break;

            case gCCCA_c:
                Radio_Phy_PlmeCcaConfirm(gPhyChannelIdle_c, mPhyInstance);
                break;

            default:
                Radio_Phy_PlmeSyncLossIndication(mPhyInstance);
                break;
            }
        }
    }
    /* Timers interrupt */
    else
    {
        /* Timer 2 Compare Match */
        if ((irqStatus & ZLL_IRQSTS_TMR2IRQ_MASK) && (!(irqStatus & ZLL_IRQSTS_TMR2MSK_MASK)))
        {
            PhyTimeDisableEventTrigger();

            if (gIdle_c != xcvseqCopy)
            {
                Dbg_IO_Set(5, 0);
                Dbg_IO_Set(5, 1);
                Radio_Phy_TimeStartEventIndication(mPhyInstance);
            }
        }

        /* Timer 3 Compare Match */
        if ((irqStatus & ZLL_IRQSTS_TMR3IRQ_MASK) && (!(irqStatus & ZLL_IRQSTS_TMR3MSK_MASK)))
        {
            PhyTimeDisableEventTimeout();

            /* Ensure that we're not issuing TimeoutIndication while the Automated sequence is still in progress */
            /* TMR3 can expire during R-T turnaround for example, case in which the sequence is not interrupted */
            if (gIdle_c == xcvseqCopy)
            {
                Radio_Phy_TimeRxTimeoutIndication(mPhyInstance);
            }
        }

        /* Timer 4 Compare Match */
        if ((irqStatus & ZLL_IRQSTS_TMR4IRQ_MASK) && (!(irqStatus & ZLL_IRQSTS_TMR4MSK_MASK)))
        {
            /* Disable TMR4 comparator */
            ZLL->PHY_CTRL &= ~ZLL_PHY_CTRL_TMR4CMP_EN_MASK;
            /* Mask and clear TMR4 interrupt (do not change other IRQ status) */
            irqStatus &= ~( ZLL_IRQSTS_TMR1IRQ_MASK |
                            ZLL_IRQSTS_TMR2IRQ_MASK |
                            ZLL_IRQSTS_TMR3IRQ_MASK );
            irqStatus |= ZLL_IRQSTS_TMR4IRQ_MASK | ZLL_IRQSTS_TMR4MSK_MASK;
            ZLL->IRQSTS = irqStatus;
            PhyTime_TMR4Callback();
        }
    }

    /* Clear filter fail indication in case it remains untreated, for example in case of an enhanced  ack */
    filter_fail = 0;
    
    Radio_Phy_Notify();
}

/*! *********************************************************************************
* \brief  This function enables the Phy ISR
*
********************************************************************************** */
void PHY_Enable(void)
{
#if defined(FFU_PHY_ONLY_OVER_IMU)
    os_InterruptCreate(gPhyIrqNo_d, FFU_ZIGBEE_INT_IRQHandler);

    os_InterruptMaskClear(gPhyIrqNo_d);
    os_InterruptMaskSet(gPhyIrqNo_d);

    os_InterruptSetPriority(gPhyIrqNo_d, gPhyIrqPriority_c);
    UnprotectFromXcvrInterrupt();

#else
    /* Keep this code commented in case ram vector table support is added later on */
    //OSA_InstallIntHandler(gPhyIrqNo_d, PHY_InterruptHandler);
    NVIC_ClearPendingIRQ(gPhyIrqNo_d);
    NVIC_EnableIRQ(gPhyIrqNo_d);

    /* set transceiver interrupt priority */
    NVIC_SetPriority(gPhyIrqNo_d, gPhyIrqPriority_c >> (8 - __NVIC_PRIO_BITS));
    UnprotectFromXcvrInterrupt();
#endif
}

/*! *********************************************************************************
* \brief  This function disables the Phy ISR and aborts current activity
*
********************************************************************************** */
void PHY_Disable(void)
{
    PhyAbort();
#if defined(FFU_PHY_ONLY_OVER_IMU)
    os_InterruptMaskClear(gPhyIrqNo_d);
    ProtectFromXcvrInterrupt();
#else
    NVIC_DisableIRQ(gPhyIrqNo_d);
    ProtectFromXcvrInterrupt();
#endif
}

/*! *********************************************************************************
* \brief  This function forces the PHY IRQ to be triggered to run the ISR
*
********************************************************************************** */
void PHY_ForceIrqPending( void )
{
#if defined(FFU_PHY_ONLY_OVER_IMU)
    os_InterruptMaskPendingISR(gPhyIrqNo_d);
#else
    NVIC_SetPendingIRQ(gPhyIrqNo_d);
#endif
}

/*! *********************************************************************************
 * \brief Check is an XCVR IRQ is pending
 *
 * \return  TRUE if ISR pending, else FALSE
 *
 ********************************************************************************** */
bool_t PHY_isIrqPending(void)
{
    bool_t status = FALSE;
    uint32_t temp;

    if (!(ZLL->PHY_CTRL & ZLL_PHY_CTRL_TRCV_MSK_MASK))
    {
        /* Check usual ZLL IRQs */
        temp = ZLL->PHY_CTRL & ( ZLL_PHY_CTRL_SEQMSK_MASK         |
                                 ZLL_PHY_CTRL_TXMSK_MASK          |
                                 ZLL_PHY_CTRL_RXMSK_MASK          |
                                 ZLL_PHY_CTRL_CCAMSK_MASK         |
                                 ZLL_PHY_CTRL_RX_WMRK_MSK_MASK    |
                                 ZLL_PHY_CTRL_FILTERFAIL_MSK_MASK |
                                 ZLL_PHY_CTRL_PLL_UNLOCK_MSK_MASK );
        temp = temp >> ZLL_PHY_CTRL_SEQMSK_SHIFT;

        if (ZLL->IRQSTS & temp)
        {
            status = TRUE;
        }
        else
        {
            /* Check ZLL Timers IRQs */
            temp = ZLL->IRQSTS & ( ZLL_IRQSTS_TMR1IRQ_MASK |
                                   ZLL_IRQSTS_TMR2IRQ_MASK |
                                   ZLL_IRQSTS_TMR3IRQ_MASK |
                                   ZLL_IRQSTS_TMR4IRQ_MASK );
            temp = temp >> 4;

            if (ZLL->IRQSTS & temp)
            {
                status = TRUE;
            }
        }
    }

    return status;
}

/*! *********************************************************************************
*************************************************************************************
* Private functions
*************************************************************************************
********************************************************************************* */

/*! *********************************************************************************
* \brief  Fill the Rx parameters: RSSI, LQI, Timestamp and PSDU length
*
********************************************************************************** */
static void Phy_GetRxParams(void)
{
    if (NULL != mpRxParams)
    {
        mpRxParams->rssi = (ZLL->LQI_AND_RSSI & ZLL_LQI_AND_RSSI_RSSI_MASK) >> ZLL_LQI_AND_RSSI_RSSI_SHIFT;
        mPhyLastRxLQI = (ZLL->LQI_AND_RSSI & ZLL_LQI_AND_RSSI_LQI_VALUE_MASK) >> ZLL_LQI_AND_RSSI_LQI_VALUE_SHIFT;
        mpRxParams->linkQuality = Phy_LqiConvert(mPhyLastRxLQI);

        mpRxParams->timeStamp = ZLL->TIMESTAMP >> ZLL_TIMESTAMP_TIMESTAMP_SHIFT;
        mpRxParams->psduLength = (ZLL->IRQSTS & ZLL_IRQSTS_RX_FRAME_LENGTH_MASK) >> ZLL_IRQSTS_RX_FRAME_LENGTH_SHIFT; /* Including FCS (2 bytes) */
        mpRxParams = NULL;
    }
}

/*! *********************************************************************************
* \brief  Scales LQI to 0-255 because HW LQI is not reported over the entire range.
*         To reach values close to 255 the reported RSSI should have a positive value but
*         this doesn't happen in real conditions. In our case, we consider RSSI values closer to 0 
*         as being very good so we want the LQI values to be larger than what is being reported.
*
* \param[in]  hwLqi  the LQI reported by HW
*
* \return  uint8_t  the LQI scaled in 0x00-0xFF
*
********************************************************************************** */
static uint8_t Phy_LqiConvert(uint8_t hwLqi)
{
#if defined(RW610N_BT_CM3_SERIES)

    int16_t lqi_final_tmp = 0;
    uint8_t lqi_final = 0;
    uint8_t snr_rd = 0;
    int16_t rssi = 0;
    
    if (hwLqi < LQI_REG_MAX_VAL)
    {
        lqi_final = hwLqi;
    }
    else
    {
        rssi = (XCVR_RX_DIG->NB_RSSI_RES0 & XCVR_RX_DIG_NB_RSSI_RES0_RSSI_NB_MASK) >> XCVR_RX_DIG_NB_RSSI_RES0_RSSI_NB_SHIFT;
        /* As the RSSI register value is only 9bit signed value, need to extend the sign value for 16bit */
        rssi = (rssi & 0x0100) ? (rssi | 0xFF00):(rssi & 0x00FF);

        if(rssi > CLIP_LQI_VAL)
        {
            lqi_final = LQI_MAX_VAL;
        }
        else
        {
            XCVR_RX_DIG->NB_RSSI_CTRL1 = XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_RSSI_WEIGHT(0x4) |\
                                 XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_SNR_WEIGHT(0x5) |\
                                 XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_BIAS(0x6) |\
                                 XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_RSSI_SENS_ADJ(0x0);

            /* Get NB RSSI control values */
            uint8_t wt_snr = (XCVR_RX_DIG->NB_RSSI_CTRL1 & XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_SNR_WEIGHT_MASK) \
                             >> XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_SNR_WEIGHT_SHIFT;
            uint8_t wt_rssi = (XCVR_RX_DIG->NB_RSSI_CTRL1 & XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_RSSI_WEIGHT_MASK) \
                              >> XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_RSSI_WEIGHT_SHIFT;
            uint8_t lqi_bias = (XCVR_RX_DIG->NB_RSSI_CTRL1 & XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_BIAS_MASK) \
                               >> XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_BIAS_SHIFT;
            uint8_t lqi_rssi_sens = (XCVR_RX_DIG->NB_RSSI_CTRL1 & XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_RSSI_SENS_ADJ_MASK) \
                                    >> XCVR_RX_DIG_NB_RSSI_CTRL1_LQI_RSSI_SENS_ADJ_SHIFT;

            snr_rd = (XCVR_RX_DIG->NB_RSSI_RES1 & XCVR_RX_DIG_NB_RSSI_RES1_SNR_NB_MASK) >> XCVR_RX_DIG_NB_RSSI_RES1_SNR_NB_SHIFT;

            /* Statement implements following formula as a SW WAR for A1 ECO_18 fix [GLC8993-450, GLC8993-450] *
            * LQI = (RSSI - (-103 + LQI_RSSI_SENS))*wRSSI + SNR*wSNR + (-36 + 2*LQI_BIAS)         *
            * RSSI Weight For LQI Calulation - Ref: d_ip_15p4_radio_syn_bg doc                    *
            * RSSI result weight in LQI calculation. Actual weight is: 2 + 0.125 * LQI_SNR_WEIGHT *
            * SNR Weight For LQI Calulation - Ref: d_ip_15p4_radio_syn_bg doc                     *
            * SNR result weight in LQI calculation. When LQI_SNR_WEIGHT!=0, the actual            *
            * weight value is: 1 + 0.125 * LQI_SNR_WEIGHT.                                        *
            * When LQI_SNR_WEIGHT=0, the actual weight value is 0.                                */

            lqi_final_tmp = (103 - lqi_rssi_sens + rssi * 1.0) * (2 + 0.125 * wt_rssi) \
                             + snr_rd / 2 * (1 + wt_snr * 0.125) * (wt_snr != 0 ? 1.0 : 0.0) \
                             + ( -36 + 2 * lqi_bias);
            /* Saturate LQI value to 1Byte */
            lqi_final = (lqi_final_tmp & LQI_MAX_VAL);
        }
    }

    return lqi_final;
/* Code retained for K4 */
#else
    if (hwLqi >= 195)
    {
        hwLqi = 255;
    }
    else
    {
        /* using the same scaling value as KW41 */
        hwLqi = (51 * hwLqi) / 44;
    }

    return hwLqi;
#endif    
}
