/*
 * Copyright (c) 2007-2015 Freescale Semiconductor, Inc.
 * Copyright 2020 NXP
 *
 * License: NXP LA_OPT_NXP_Software_License
 *
 * NXP Confidential. This software is owned or controlled by NXP and may
 * only be used strictly in accordance with the applicable license terms.
 * By expressly accepting such terms or by downloading, installing,
 * activating and/or otherwise using the software, you are agreeing that
 * you have read, and that you agree to comply with and are bound by,
 * such license terms.  If you do not agree to be bound by the applicable
 * license terms, then you may not retain, install, activate or otherwise
 * use the software.  This code may only be used in a microprocessor,
 * microcontroller, sensor or digital signal processor ("NXP Product")
 * supplied directly or indirectly from NXP.  See the full NXP Software
 * License Agreement in license/LA_OPT_NXP_Software_License.pdf
 *
 * FreeMASTER Communication Driver - DSC JTAG EOnCE RTD Unit Communication Interface
 */
#include "freemaster.h"
#include "freemaster_private.h"

/* Numeric identifier to help pre-processor to identify whether our driver is used or not. */
#define FMSTR_SERIAL_56F800E_EONCE_ID 1

#if (FMSTR_MK_IDSTR(FMSTR_SERIAL_DRV) == FMSTR_SERIAL_56F800E_EONCE_ID)
#include "freemaster_serial.h"
#include "freemaster_56f800e_eonce.h"

#if FMSTR_DISABLE == 0

/***********************************
 *  configuration check
 ***********************************/

#if FMSTR_USE_EONCE_TDF_WORKAROUND > 0 && FMSTR_POLL_DRIVEN == 0
#error FreeMASTER EOnCE communication with active TDF big workaround only works in polled mode.
#endif

/***********************************
 *  local variables
 ***********************************/

/* EOnCE bas address is the same for all DSC platforms */
#define FMSTR_EONCE_BASE 0xFFFF00U

/* EOnCE base address kept as U32 word, but still it needs the LDM to work. */
#if defined(FMSTR_SERIAL_BASE) && ((FMSTR_SERIAL_BASE) != FMSTR_EONCE_BASE)
#warning FMSTR_SERIAL_BASE should not be specified when using JTAG EOnCE RTD Unit (undefine to use default 0xFFFF00)
static FMSTR_U32 fmstr_eonceBaseAddr = (FMSTR_U32)FMSTR_SERIAL_BASE;
#else
static FMSTR_U32 fmstr_eonceBaseAddr = 0;
#endif

struct
{
    FMSTR_U32 txData;   /* Cached 4 bytes of transmit data */
    FMSTR_U32 rxData;   /* Cached 4 bytes of receive data */
    FMSTR_SIZE8 txSize; /* Number of bytes in transmit data cache */
    FMSTR_SIZE8 rxSize; /* Number of bytes in receive data cache */

    union
    {
        FMSTR_FLAGS all;
        struct
        {
            unsigned riePending : 1;  /* RX interrupt is pending to be enabled */
            unsigned tdfNeedRead : 1; /* used with FMSTR_USE_EONCE_TDF_WORKAROUND logic */
        } bit;
    } flags;

} fmstr_eonceCtx;

/***********************************
 *  local function prototypes
 ***********************************/

/* Interface function - Initialization of EONCE driver adapter */
static FMSTR_BOOL _FMSTR_56F800E_EOnCE_Init(void);
static void _FMSTR_56F800E_EOnCE_EnableTransmit(FMSTR_BOOL enable);
static void _FMSTR_56F800E_EOnCE_EnableReceive(FMSTR_BOOL enable);
static void _FMSTR_56F800E_EOnCE_EnableTransmitInterrupt(FMSTR_BOOL enable);
static void _FMSTR_56F800E_EOnCE_EnableReceiveInterrupt(FMSTR_BOOL enable);
static void _FMSTR_56F800E_EOnCE_EnableTransmitCompleteInterrupt(FMSTR_BOOL enable);
static FMSTR_BOOL _FMSTR_56F800E_EOnCE_IsTransmitRegEmpty(void);
static FMSTR_BOOL _FMSTR_56F800E_EOnCE_IsReceiveRegFull(void);
static FMSTR_BOOL _FMSTR_56F800E_EOnCE_IsTransmitterActive(void);
static void _FMSTR_56F800E_EOnCE_PutChar(FMSTR_BCHR ch);
static FMSTR_BCHR _FMSTR_56F800E_EOnCE_GetChar(void);
static void _FMSTR_56F800E_EOnCE_Flush(void);
static void _FMSTR_56F800E_EOnCE_Poll(void);

static FMSTR_BOOL _FMSTR_56F800E_EOnCE_ReadTDF(void);
static void _FMSTR_56F800E_EOnCE_TryReceiveData(void);
static void _FMSTR_56F800E_EOnCE_TryTransmitData(void);

/***********************************
 *  global variables
 ***********************************/
/* Interface of this EONCE driver */

const FMSTR_SERIAL_DRV_INTF FMSTR_SERIAL_56F800E_EONCE = {
    FMSTR_C99_INIT(Init) _FMSTR_56F800E_EOnCE_Init,
    FMSTR_C99_INIT(EnableTransmit) _FMSTR_56F800E_EOnCE_EnableTransmit,
    FMSTR_C99_INIT(EnableReceive) _FMSTR_56F800E_EOnCE_EnableReceive,
    FMSTR_C99_INIT(EnableTransmitInterrupt) _FMSTR_56F800E_EOnCE_EnableTransmitInterrupt,
    FMSTR_C99_INIT(EnableTransmitCompleteInterrupt) _FMSTR_56F800E_EOnCE_EnableTransmitCompleteInterrupt,
    FMSTR_C99_INIT(EnableReceiveInterrupt) _FMSTR_56F800E_EOnCE_EnableReceiveInterrupt,
    FMSTR_C99_INIT(IsTransmitRegEmpty) _FMSTR_56F800E_EOnCE_IsTransmitRegEmpty,
    FMSTR_C99_INIT(IsReceiveRegFull) _FMSTR_56F800E_EOnCE_IsReceiveRegFull,
    FMSTR_C99_INIT(IsTransmitterActive) _FMSTR_56F800E_EOnCE_IsTransmitterActive,
    FMSTR_C99_INIT(PutChar) _FMSTR_56F800E_EOnCE_PutChar,
    FMSTR_C99_INIT(GetChar) _FMSTR_56F800E_EOnCE_GetChar,
    FMSTR_C99_INIT(Flush) _FMSTR_56F800E_EOnCE_Flush,
    FMSTR_C99_INIT(Poll) _FMSTR_56F800E_EOnCE_Poll,
};

/****************************************************************************************
 * General peripheral space access macros
 *****************************************************************************************/

#define FMSTR_SETBIT(base, offset, bit) (*(volatile FMSTR_U16 *)(((FMSTR_U32)(base)) + (offset)) |= bit)
#define FMSTR_CLRBIT(base, offset, bit) \
    (*(volatile FMSTR_U16 *)(((FMSTR_U32)(base)) + (offset)) &= (FMSTR_U16) ~((FMSTR_U16)(bit)))
#define FMSTR_TSTBIT(base, offset, bit)   (*(volatile FMSTR_U16 *)(((FMSTR_U32)(base)) + (offset)) & (bit))
#define FMSTR_SETREG(base, offset, value) (*(volatile FMSTR_U16 *)(((FMSTR_U32)(base)) + (offset)) = value)
#define FMSTR_GETREG(base, offset)        (*(volatile FMSTR_U16 *)(((FMSTR_U32)(base)) + (offset)))

#define FMSTR_SETREG32(base, offset, value) (*(volatile FMSTR_U32 *)(((FMSTR_U32)(base)) + (offset)) = value)
#define FMSTR_GETREG32(base, offset)        (*(volatile FMSTR_U32 *)(((FMSTR_U32)(base)) + (offset)))

/****************************************************************************************
 * EONCE module constants
 *****************************************************************************************/

/* EONCE module registers */
#define FMSTR_EONCE_OTXRXSR_OFFSET 0xfdU
#define FMSTR_EONCE_ORX_OFFSET     0xfeU
#define FMSTR_EONCE_OTX_OFFSET     0xfeU
#define FMSTR_EONCE_ORX1_OFFSET    0xffU
#define FMSTR_EONCE_OTX1_OFFSET    0xffU

/* OTXRXSR register */
#define FMSTR_EONCE_OTXRXSR_RDF 0x01U
#define FMSTR_EONCE_OTXRXSR_TDF 0x02U
#define FMSTR_EONCE_OTXRXSR_RIE 0x04U
#define FMSTR_EONCE_OTXRXSR_TIE 0x08U

/******************************************************************************
 *
 * EONCE communication initialization
 *
 ******************************************************************************/

static FMSTR_BOOL _FMSTR_56F800E_EOnCE_Init(void)
{
    /* Default EOnCE address is used */
    if (fmstr_eonceBaseAddr == 0U)
    {
        fmstr_eonceBaseAddr = FMSTR_EONCE_BASE;
    }

    fmstr_eonceCtx.flags.all = 0U;
    fmstr_eonceCtx.txSize    = 0U;
    fmstr_eonceCtx.rxSize    = 0U;
    return FMSTR_TRUE;
}

/******************************************************************************
 *
 * EONCE polling
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_Poll(void)
{
    /* In the JTAG interrupt-driven mode (both short and long), this function periodically checks
     * if setting the JTAG RIE bit failed recently. This may happen because of the
     * RIE is held low by the EONCE hardware until the EONCE is first accessed from host.
     * The FMSTR_Init (->FMSTR_Listen) is often called while the PC-side FreeMASTER is still
     * turned off. So the JTAG is not enabled at this time and RIE bit is not set.
     * This problem is detected (see how riePending is set) and it is tried to be fixed
     * periodically here in the poll call.
     */
#if FMSTR_LONG_INTR > 0 || FMSTR_SHORT_INTR > 0
    if (fmstr_eonceCtx.flags.bit.riePending != 0U)
    {
#if FMSTR_DEBUG_LEVEL >= 3
        FMSTR_DEBUG_PRINTF("FMSTR EOnCE Late RIE attempt\n");
#endif
        _FMSTR_56F800E_EOnCE_EnableReceiveInterrupt(FMSTR_TRUE);
    }
#endif
}

/******************************************************************************
 *
 * Enable/Disable EONCE transmitter
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_EnableTransmit(FMSTR_BOOL enable)
{
    if (enable == FMSTR_FALSE)
    {
        fmstr_eonceCtx.txSize = 0;
#if FMSTR_USE_EONCE_TDF_WORKAROUND > 0
        fmstr_eonceCtx.flags.bit.tdfNeedRead = 0;
#endif
    }
}

/******************************************************************************
 *
 * Enable/Disable EONCE receiver
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_EnableReceive(FMSTR_BOOL enable)
{
    if (enable == FMSTR_FALSE)
    {
        fmstr_eonceCtx.rxSize = 0;
    }
}

/******************************************************************************
 *
 * Enable/Disable interrupt from transmit register empty event
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_EnableTransmitInterrupt(FMSTR_BOOL enable)
{
    if (enable != FMSTR_FALSE)
    {
        /* Enable interrupt */
        FMSTR_SETBIT(fmstr_eonceBaseAddr, FMSTR_EONCE_OTXRXSR_OFFSET, FMSTR_EONCE_OTXRXSR_TIE);
    }
    else
    {
        /* Disable interrupt */
        FMSTR_CLRBIT(fmstr_eonceBaseAddr, FMSTR_EONCE_OTXRXSR_OFFSET, FMSTR_EONCE_OTXRXSR_TIE);
    }
}

/******************************************************************************
 *
 * Enable/Disable interrupt when transmission is complete (=idle)
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_EnableTransmitCompleteInterrupt(FMSTR_BOOL enable)
{
    /* no such interrupt, see how TDF is tested. */
}

/******************************************************************************
 *
 * Enable/Disable interrupt from receive register full event
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_EnableReceiveInterrupt(FMSTR_BOOL enable)
{
    /* this bit will be set eventually if enabling the interrupt fails */
    fmstr_eonceCtx.flags.bit.riePending = 0U;

    if (enable != FMSTR_FALSE)
    {
        /* Enable interrupt */
        FMSTR_SETBIT(fmstr_eonceBaseAddr, FMSTR_EONCE_OTXRXSR_OFFSET, FMSTR_EONCE_OTXRXSR_RIE);

        /* On older core versions, enabling the interrupt may fail if JTAG module is not
         * yet initialized by an external access. See the Poll call for more information. */
        if (FMSTR_TSTBIT(fmstr_eonceBaseAddr, FMSTR_EONCE_OTXRXSR_OFFSET, FMSTR_EONCE_OTXRXSR_RIE) == 0U)
        {
            fmstr_eonceCtx.flags.bit.riePending = 1U;
        }
    }
    else
    {
        /* Disable interrupt */
        FMSTR_CLRBIT(fmstr_eonceBaseAddr, FMSTR_EONCE_OTXRXSR_OFFSET, FMSTR_EONCE_OTXRXSR_RIE);
    }
}

/******************************************************************************
 *
 * Returns TRUE if the transmit register is empty, and it's possible to put next char
 *
 ******************************************************************************/

static FMSTR_BOOL _FMSTR_56F800E_EOnCE_IsTransmitRegEmpty(void)
{
    /* Any space in transmit buffer? */
    if (fmstr_eonceCtx.txSize < 4U)
    {
        return FMSTR_TRUE;
    }

    /* Try to flush 4 bytes which are pending in our output buffer now */
    _FMSTR_56F800E_EOnCE_TryTransmitData();

    /* Any free space by now? */
    return fmstr_eonceCtx.txSize < 4U;
}

/******************************************************************************
 *
 * Try to receive some EOnCE data from the remote peer into the local buffer
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_TryReceiveData(void)
{
    if (FMSTR_TSTBIT(fmstr_eonceBaseAddr, FMSTR_EONCE_OTXRXSR_OFFSET, FMSTR_EONCE_OTXRXSR_RDF) != 0U)
    {
        fmstr_eonceCtx.rxData = FMSTR_GETREG32(fmstr_eonceBaseAddr, FMSTR_EONCE_ORX_OFFSET);
        fmstr_eonceCtx.rxSize = 4U;

#if FMSTR_DEBUG_LEVEL >= 3
        FMSTR_DEBUG_PRINTF("FMSTR EOnCE RX %04lx\n", fmstr_eonceCtx.rxData);
#endif
    }
}

/******************************************************************************
 *
 * Returns TRUE if the receive register is full, and it's possible to get received char
 *
 ******************************************************************************/

static FMSTR_BOOL _FMSTR_56F800E_EOnCE_IsReceiveRegFull(void)
{
    /* Still some characters in the receive buffer? */
    if (fmstr_eonceCtx.rxSize > 0U)
    {
        return FMSTR_TRUE;
    }

    /* Try to get next 4 bytes */
    _FMSTR_56F800E_EOnCE_TryReceiveData();

    /* Any data by now? */
    return fmstr_eonceCtx.rxSize > 0U;
}

/******************************************************************************
 *
 * Low-level testing of TDF flag
 *
 ******************************************************************************/

static FMSTR_BOOL _FMSTR_56F800E_EOnCE_ReadTDF(void)
{
    FMSTR_U16 statusReg = FMSTR_GETREG(fmstr_eonceBaseAddr, FMSTR_EONCE_OTXRXSR_OFFSET);

    /* The TDF=0 indicates the transmitter has completed the physical transmission. */
    if ((statusReg & FMSTR_EONCE_OTXRXSR_TDF) == 0U)
    {
#if FMSTR_USE_EONCE_TDF_WORKAROUND > 0
        if (fmstr_eonceCtx.flags.bit.tdfNeedRead != 0U)
        {
            /* Silicon bug in older DSC parts makes TDF bit unusable (TDF gets reset too early).
               The FreeMASTER tool sends a dummy confirmation word whenever it receives
               word transmitted by us. This means we can use the RDF as an indication of !TDF */
            if ((statusReg & FMSTR_EONCE_OTXRXSR_RDF) != 0U)
            {
                /* Read and discard the data. */
                FMSTR_U32 dummyRx = FMSTR_GETREG32(fmstr_eonceBaseAddr, FMSTR_EONCE_ORX_OFFSET);
#if FMSTR_DEBUG_LEVEL >= 3
                FMSTR_DEBUG_PRINTF("FMSTR EOnCE Dummy RX for !TDF %04lx\n", dummyRx);
#endif
                fmstr_eonceCtx.flags.bit.tdfNeedRead = 0U;
                return FMSTR_FALSE;
            }
            else
            {
                return FMSTR_TRUE; /* Not received anything, transmitter is still active. */
            }
        }
        else
        {
            /* We only check the RDF once per each transmitted word */
            return FMSTR_FALSE;
        }
#else
        /* Modern DSC parts should have the bug fixed, so we can rely on the TDF bit. */
        return FMSTR_FALSE;
#endif
    }

    /* Transmitter is still active. */
    return FMSTR_TRUE;
}

/******************************************************************************
 *
 * Returns TRUE if the transmitter is still active
 *
 ******************************************************************************/

static FMSTR_BOOL _FMSTR_56F800E_EOnCE_IsTransmitterActive(void)
{
    /* this call should only be used after flush */
    FMSTR_ASSERT(fmstr_eonceCtx.txSize == 0U || fmstr_eonceCtx.txSize == 4U);

    /* Try physical flushing if word is still pending */
    if (fmstr_eonceCtx.txSize == 4U)
    {
        _FMSTR_56F800E_EOnCE_TryTransmitData();
    }

    return (FMSTR_BOOL)(fmstr_eonceCtx.txSize > 0U || _FMSTR_56F800E_EOnCE_ReadTDF() != FMSTR_FALSE);
}

/******************************************************************************
 *
 * The function puts the char for transmit
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_PutChar(FMSTR_BCHR ch)
{
    /* There should be space */
    if (fmstr_eonceCtx.txSize < 4U)
    {
        fmstr_eonceCtx.txData = (fmstr_eonceCtx.txData << 8) | (ch & 0xffU);
        fmstr_eonceCtx.txSize++;

        if (fmstr_eonceCtx.txSize == 4U)
        {
            _FMSTR_56F800E_EOnCE_TryTransmitData();
        }
    }
    else
    {
        /* Caller hasn't checked if IsTransmitRegEmpty */
        FMSTR_ASSERT(0 == 1);
    }
}

/******************************************************************************
 *
 * The function gets the received char
 *
 ******************************************************************************/
static FMSTR_BCHR _FMSTR_56F800E_EOnCE_GetChar(void)
{
    FMSTR_BCHR ch = 0U;

    if (fmstr_eonceCtx.rxSize > 0U)
    {
        ch = (FMSTR_BCHR)((fmstr_eonceCtx.rxData >> 24) & 0xffU);
        fmstr_eonceCtx.rxData <<= 8;
        fmstr_eonceCtx.rxSize--;
    }
    else
    {
        /* Caller hasn't checked if IsReceiveRegFull */
        FMSTR_ASSERT(0 == 1);
    }

    return ch;
}

/******************************************************************************
 *
 * Sends buffered 32bit word
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_TryTransmitData(void)
{
    /* This should only be used when full word is ready */
    FMSTR_ASSERT(fmstr_eonceCtx.txSize == 4U);

    /* Is it possible to transmit physically now? */
    if (_FMSTR_56F800E_EOnCE_ReadTDF() == FMSTR_FALSE)
    {
#if FMSTR_DEBUG_LEVEL >= 3
        FMSTR_DEBUG_PRINTF("FMSTR EOnCE TX %04lx\n", fmstr_eonceCtx.txData);
#endif
        /* OK put to physical transmit buffer */
        FMSTR_SETREG32(fmstr_eonceBaseAddr, FMSTR_EONCE_OTX_OFFSET, fmstr_eonceCtx.txData);
        fmstr_eonceCtx.txSize = 0U;

        /* To fix the TDF behavior, we will perform dummy reading. */
#if FMSTR_USE_EONCE_TDF_WORKAROUND > 0
        fmstr_eonceCtx.flags.bit.tdfNeedRead = 1U;
#endif
    }
}

/******************************************************************************
 *
 * Send buffered data
 *
 ******************************************************************************/

static void _FMSTR_56F800E_EOnCE_Flush(void)
{
    /* If anything pending to be transmitted. */
    if (fmstr_eonceCtx.txSize != 0U)
    {
        /* Send dummy 0xff bytes to wrap up to 4 bytes */
        if (fmstr_eonceCtx.txSize < 4U)
        {
            FMSTR_SIZE8 shift     = 8U * (4U - fmstr_eonceCtx.txSize);
            fmstr_eonceCtx.txData = (fmstr_eonceCtx.txData << shift) | ((1 << shift) - 1);
            fmstr_eonceCtx.txSize = 4U;
        }

        /* Physical flush if possible. */
        _FMSTR_56F800E_EOnCE_TryTransmitData();
    }
}

/******************************************************************************
 *
 * Assign FreeMASTER communication module base address
 *
 ******************************************************************************/

void FMSTR_SerialSetBaseAddress(FMSTR_U32 base)
{
    fmstr_eonceBaseAddr = base;
}

/******************************************************************************
 *
 * Process FreeMASTER serial interrupt (call this function from EONCE ISR)
 *
 ******************************************************************************/

void FMSTR_SerialIsr(void)
{
#if FMSTR_LONG_INTR > 0 || FMSTR_SHORT_INTR > 0
    /* process incoming or just transmitted byte */
    FMSTR_ProcessSerial();
#endif
}

#else /* !(FMSTR_DISABLE) */

/* Empty API functions when FMSTR_DISABLE is set */
void FMSTR_SerialSetBaseAddress(FMSTR_U32 base)
{
    FMSTR_UNUSED(base);
}

void FMSTR_SerialIsr(void)
{
}

#endif /* !(FMSTR_DISABLE) */
#endif /* (FMSTR_MK_IDSTR(FMSTR_SERIAL_DRV) == FMSTR_SERIAL_56F800E_EONCE_ID) */
