/*
 * Copyright 2023 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_common.h"
#include "fsl_glikey.h"

/*******************************************************************************
 * Definitions
 *******************************************************************************/

/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "platform.drivers.glikey"
#endif

#define GLIKEY_SFR_LOCK   0x05u
#define GLIKEY_SFR_UNLOCK 0x0Au

/* Define to select between write to CTRL0_WR_0 or CTRL1_WR_1 */
#define WR_EN0 (0x0Fu)
#define WR_EN1 (0xF0u)

#define WR_EN0_VALUE_SHIFT (0u)
#define WR_EN1_VALUE_SHIFT (16u)

#define CTRL_SEL_CODEWORD_SHIFT (24u)

#define WR_0_INIT (0x2u)
#define WR_1_INIT (0x0u)

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
static inline void Glikey_Internal_Set_WR_0(GLIKEY_Type *base, uint32_t value);
static inline void Glikey_Internal_Set_WR_1(GLIKEY_Type *base, uint32_t value);
/*******************************************************************************
 * Code
 ******************************************************************************/

/*!
 * Weak implementation of GLIKEY IRQ, should be re-defined by user when using GLIKEY IRQ

__WEAK void GLIKEY0_DriverIRQHandler(void)
{
    GLIKEY generates IRQ until corresponding bit in STATUS is cleared by calling
    GLIKEY_ClearStatusFlags();
}
*/

static inline void Glikey_Internal_Set_WR_0(GLIKEY_Type *base, uint32_t value)
{
    uint32_t ctrl0 = ((GLIKEY_Type *)base)->CTRL_0;

    /* Clear WR_0 */
    ctrl0 = ctrl0 & (~GLIKEY_CTRL_0_WR_EN_0_MASK);

    /* Assign value */
    ((GLIKEY_Type *)base)->CTRL_0 = ctrl0 | GLIKEY_CTRL_0_WR_EN_0(value);
}

static inline void Glikey_Internal_Set_WR_1(GLIKEY_Type *base, uint32_t value)
{
    uint32_t ctrl1 = ((GLIKEY_Type *)base)->CTRL_1;

    /* Clear WR_1 */
    ctrl1 = ctrl1 & (~GLIKEY_CTRL_1_WR_EN_1_MASK);

    /* Assign value */
    ((GLIKEY_Type *)base)->CTRL_1 = ctrl1 | GLIKEY_CTRL_1_WR_EN_1(value);
}

uint32_t GLIKEY_GetStatus(GLIKEY_Type *base)
{
    return ((GLIKEY_Type *)base)->STATUS;
}

status_t GLIKEY_IsLocked(GLIKEY_Type *base)
{
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked == retCode)
    {
        return kStatus_GLIKEY_NotLocked;
    }
    else
    {
        return kStatus_GLIKEY_Locked;
    }
}

status_t GLIKEY_CheckLock(GLIKEY_Type *base)
{
    uint32_t sfrLock = (((GLIKEY_Type *)base)->CTRL_1 & GLIKEY_CTRL_1_SFR_LOCK_MASK) >> GLIKEY_CTRL_1_SFR_LOCK_SHIFT;

    if (GLIKEY_SFR_UNLOCK != sfrLock) /* Locked */
    {
        return kStatus_GLIKEY_LockedError;
    }

    return kStatus_GLIKEY_NotLocked;
}

#if defined(GLIKEY_VERSION_FSM_CONFIG)
status_t GLIKEY_GetVersion(GLIKEY_Type *base, uint32_t *result)
{
    *result = ((GLIKEY_Type *)base)->VERSION;

    return kStatus_Success;
}
#endif /* GLIKEY_VERSION_FSM_CONFIG */

status_t GLIKEY_SyncReset(GLIKEY_Type *base)
{
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    /* Set CTRL_0.SFT_RST = 1 */
    ((GLIKEY_Type *)base)->CTRL_0 |= GLIKEY_CTRL_0_SFT_RST_MASK;

    return kStatus_Success;
}

status_t GLIKEY_SetIntEnable(GLIKEY_Type *base, uint32_t value)
{
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    ((GLIKEY_Type *)base)->INTR_CTRL = GLIKEY_INTR_CTRL_INT_EN(value);

    return kStatus_Success;
}

status_t GLIKEY_GetIntEnable(GLIKEY_Type *base, uint32_t *value)
{
    *value = ((GLIKEY_Type *)base)->INTR_CTRL & GLIKEY_INTR_CTRL_INT_EN_MASK;

    return kStatus_Success;
}

status_t GLIKEY_ClearIntStatus(GLIKEY_Type *base)
{
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    ((GLIKEY_Type *)base)->INTR_CTRL |= GLIKEY_INTR_CTRL_INT_CLR_MASK;

    return kStatus_Success;
}

status_t GLIKEY_SetIntStatus(GLIKEY_Type *base)
{
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    ((GLIKEY_Type *)base)->INTR_CTRL |= GLIKEY_INTR_CTRL_INT_SET_MASK;

    return kStatus_Success;
}

status_t GLIKEY_Lock(GLIKEY_Type *base)
{
    /* Check if SFR_LOCK is locked */
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode) /* Glikey is not locked -> lock */
    {
        uint32_t ctrl1 = ((GLIKEY_Type *)base)->CTRL_1;
        /* Clear SFR_LOCK */
        ctrl1 = ctrl1 & (~GLIKEY_CTRL_1_SFR_LOCK_MASK);
        /* Lock Gliekey */
        ((GLIKEY_Type *)base)->CTRL_1 = ctrl1 | GLIKEY_CTRL_1_SFR_LOCK(GLIKEY_SFR_LOCK);
    }

    return kStatus_Success;
}

status_t GLIKEY_IsIndexLocked(GLIKEY_Type *base, uint32_t index)
{
    /* Set CTRL_1.READ_INDEX = index */
    uint32_t ctr1Val = ((GLIKEY_Type *)base)->CTRL_1;
    /* Clear READ_INDEX */
    ctr1Val = ctr1Val & (~GLIKEY_CTRL_1_READ_INDEX_MASK);
    /* Set index value */
    ((GLIKEY_Type *)base)->CTRL_1 = ctr1Val | GLIKEY_CTRL_1_READ_INDEX(index);

    /* Check ERROR_STATUS */
    if (0u != (GLIKEY_GetStatus(base) & GLIKEY_STATUS_ERROR_STATUS_MASK))
    {
        return kStatus_Fail;
    }

    /* Check if STATUS.LOCK_STATUS is 0 */
    if (0u == (GLIKEY_GetStatus(base) & GLIKEY_STATUS_LOCK_STATUS_MASK))
    {
        return kStatus_GLIKEY_NotLocked;
    }
    else
    {
        return kStatus_GLIKEY_Locked;
    }
}

status_t GLIKEY_LockIndex(GLIKEY_Type *base)
{
    /* Check if Glikey SFR locked */
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    /* Check if FSM is in WR_EN state */
    uint32_t state = (GLIKEY_GetStatus(base) & GLIKEY_STATUS_FSM_STATE_MASK) >> GLIKEY_STATUS_FSM_STATE_SHIFT;
    if (GLIKEY_FSM_WR_EN == state)
    {
        /* Set WR_EN0 = b11 */
        ((GLIKEY_Type *)base)->CTRL_0 |= GLIKEY_CTRL_0_WR_EN_0(0x3u);
        return kStatus_GLIKEY_Locked;
    }
    else
    {
        return kStatus_GLIKEY_DisabledError;
    }
}

status_t GLIKEY_StartEnable(GLIKEY_Type *base, uint32_t index)
{
    status_t retCode = GLIKEY_CheckLock(base);
    uint32_t ctrl0 = 0u, ctrl1 = 0u;

    /* Check if Glikey SFR locked */
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    /* Check if index is locked */
    retCode = GLIKEY_IsIndexLocked(base, index);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    /* Return to INIT if LOCKED (it can happen after SFR reset if previous write index was locked) */
    if (GLIKEY_FSM_LOCKED == (GLIKEY_GetStatus(base) & GLIKEY_STATUS_FSM_STATE_MASK) >> GLIKEY_STATUS_FSM_STATE_SHIFT)
    {
        /* LOCKED -> INIT */
        ctrl0 = ((GLIKEY_Type *)base)->CTRL_0;
        /* Clear old index */
        ctrl0 = ctrl0 & (~GLIKEY_CTRL_0_WRITE_INDEX_MASK);
        /* Set new index */
        ctrl0 |= GLIKEY_CTRL_0_WRITE_INDEX(index);
        /* Set CTRL0.WR_EN_0 = 0x2 */
        ctrl0 = (ctrl0 & (~GLIKEY_CTRL_0_WR_EN_0_MASK)) | GLIKEY_CTRL_0_WR_EN_0(2U);
        /* Write to CTRL0 (new index and WR_EN_0 = 0x2) */
        ((GLIKEY_Type *)base)->CTRL_0 = ctrl0;

        /* CTRL1 */
        ctrl1 = ((GLIKEY_Type *)base)->CTRL_1;
        /* Clear CTRL1.WR_EN_1 */
        ctrl1 &= ~GLIKEY_CTRL_1_WR_EN_1_MASK;
        ((GLIKEY_Type *)base)->CTRL_1 = ctrl1;
    }

    /* Clear old CTRL0.WR_EN_0 */
    ctrl0 = ctrl0 & (~GLIKEY_CTRL_0_WR_EN_0_MASK);
    /* Set new index */
    ctrl0 |= GLIKEY_CTRL_0_WRITE_INDEX(index);

    /* Start the enable process by writting 0x01 to CTRL0.WR_EN_0 */
    ctrl0 = ctrl0 | ((uint32_t)0x01u << GLIKEY_CTRL_0_WR_EN_0_SHIFT);

    /* Write to CTRL0 (new index and WR_EN_0 = 0x01) */
    ((GLIKEY_Type *)base)->CTRL_0 = ctrl0;

    /* CTRL1 */
    ctrl1 = ((GLIKEY_Type *)base)->CTRL_1;
    /* Clear CTRL1.WR_EN_1 */
    ctrl1 &= ~GLIKEY_CTRL_1_WR_EN_1_MASK;
    ((GLIKEY_Type *)base)->CTRL_1 = ctrl1;

    return kStatus_Success;
}

status_t GLIKEY_ContinueEnable(GLIKEY_Type *base, uint32_t codeword)
{
    /* Check if Glikey SFR locked */
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    /* Set correctly we_en0 and wr_en1 according to codeword */
    /* Select between CTRL0/CTRL1 */
    if (WR_EN1 == (codeword >> CTRL_SEL_CODEWORD_SHIFT))
    {
        Glikey_Internal_Set_WR_1(base, codeword >> WR_EN1_VALUE_SHIFT);
    }
    else if (WR_EN0 == (codeword >> CTRL_SEL_CODEWORD_SHIFT))
    {
        Glikey_Internal_Set_WR_0(base, codeword >> WR_EN0_VALUE_SHIFT);
    }
    else
    {
        return kStatus_Fail;
    }

    if (0u != (GLIKEY_GetStatus(base) & GLIKEY_STATUS_ERROR_STATUS_MASK))
    {
        return kStatus_GLIKEY_DisabledError;
    }

    /* Validate codeword - check if XXYZ^UUVW == 0xFFFF */
    if (0xFFFFu != (((codeword) ^ (codeword >> 16u)) & 0xFFFFu))
    {
        return kStatus_Fail;
    }

    return kStatus_Success;
}

status_t GLIKEY_EndOperation(GLIKEY_Type *base)
{
    /* Check if Glikey SFR locked */
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    // check state
    uint32_t state = (GLIKEY_GetStatus(base) & GLIKEY_STATUS_FSM_STATE_MASK) >> GLIKEY_STATUS_FSM_STATE_SHIFT;

    switch (state)
    {
        case GLIKEY_FSM_LOCKED:
        {
            /* If locked -> try to go to init with wr_en0 = WR_0_INIT (0x02); wr_en1 = WR_1_INIT (0x00) */
            Glikey_Internal_Set_WR_0(base, WR_0_INIT);
            Glikey_Internal_Set_WR_1(base, WR_1_INIT);

            /* if still locked return locked - need to change index */
            if (GLIKEY_FSM_LOCKED ==
                (GLIKEY_GetStatus(base) & GLIKEY_STATUS_FSM_STATE_MASK) >> GLIKEY_STATUS_FSM_STATE_SHIFT)
            {
                return kStatus_GLIKEY_Locked;
            }

            retCode = kStatus_Success;
            break;
        }

        case GLIKEY_FSM_SSR_RESET:
        case GLIKEY_FSM_WR_EN:
        {
            /* If FSM in RESET -> wr_en0 = WR_0_INIT; wr_en1 = WR_1_INIT */
            /* If FSM in WR_EN -> wr_en0 = WR_0_INIT; wr_en1 = WR_1_INIT */
            Glikey_Internal_Set_WR_0(base, WR_0_INIT);
            Glikey_Internal_Set_WR_1(base, WR_1_INIT);

            retCode = kStatus_Success;
            break;
        }

        default:
            /* Disabled error */
            retCode = kStatus_GLIKEY_DisabledError;
            break;
    }

    return retCode;
}

status_t GLIKEY_ResetIndex(GLIKEY_Type *base, uint32_t index)
{
    /* Check if Glikey SFR locked */
    status_t retCode = GLIKEY_CheckLock(base);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    /* Check if index is locked */
    retCode = GLIKEY_IsIndexLocked(base, index);
    if (kStatus_GLIKEY_NotLocked != retCode)
    {
        return retCode;
    }

    /* Check if FSM is in INIT state */
    uint32_t state = (GLIKEY_GetStatus(base) & GLIKEY_STATUS_FSM_STATE_MASK) >> GLIKEY_STATUS_FSM_STATE_SHIFT;
    if (GLIKEY_FSM_INIT == state)
    {
        /* CTRL0.WRITE_INDEX = index */
        uint32_t ctrl0 = ((GLIKEY_Type *)base)->CTRL_0;
        /* Clear index */
        ctrl0 = ctrl0 & (~GLIKEY_CTRL_0_WRITE_INDEX_MASK);
        /* Write new value */
        ((GLIKEY_Type *)base)->CTRL_0 = ctrl0 | GLIKEY_CTRL_0_WRITE_INDEX(index);

        return kStatus_Success;
    }
    else
    {
        return kStatus_GLIKEY_DisabledError;
    }
}
