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

#include "fsl_bee.h"

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

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

/*******************************************************************************
 * Variables
 ******************************************************************************/

/*******************************************************************************
 * Code
 ******************************************************************************/

static void aligned_memcpy(void *dst, const void *src, size_t size)
{
    register uint32_t *to32         = (uint32_t *)(uint32_t *)dst;
    register const uint32_t *from32 = (const uint32_t *)(const uint32_t *)src;

    while (size >= sizeof(uint32_t))
    {
        *to32 = *from32;
        size -= sizeof(uint32_t);
        to32++;
        from32++;
    }
}

/*!
 * brief Resets BEE module to factory default values.
 *
 * This function performs hardware reset of BEE module. Attributes and keys from software for both regions are cleared.
 *
 * param base BEE peripheral address.
 */
void BEE_Init(BEE_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    CLOCK_EnableClock(kCLOCK_Bee);
#endif

    base->CTRL = BEE_CTRL_CTRL_SFTRST_N_MASK | BEE_CTRL_CTRL_CLK_EN_MASK;
}

/*!
 * brief Resets BEE module, clears keys for both regions and disables clock to the BEE.
 *
 * This function performs hardware reset of BEE module and disables clocks. Attributes and keys from software for both
 * regions are cleared.
 *
 * param base BEE peripheral address.
 */
void BEE_Deinit(BEE_Type *base)
{
    base->CTRL &=
        ~(BEE_CTRL_BEE_ENABLE_MASK | BEE_CTRL_CTRL_SFTRST_N_MASK | BEE_CTRL_CTRL_CLK_EN_MASK | BEE_CTRL_KEY_VALID_MASK);

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    CLOCK_DisableClock(kCLOCK_Bee);
#endif
}

/*!
 * brief Loads default values to the BEE region configuration structure.
 *
 * Loads default values to the BEE region configuration structure. The default values are as follows:
 * code
 *   config->region0Mode = kBEE_AesCtrMode;
 *   config->region1Mode = kBEE_AesCtrMode;
 *   config->region0AddrOffset = 0U;
 *   config->region1AddrOffset = 0U;
 *   config->region0SecLevel = kBEE_SecurityLevel3;
 *   config->region1SecLevel = kBEE_SecurityLevel3;
 *   config->region1Bot = 0U;
 *   config->region1Top = 0U;
 *   config->accessPermission = kBEE_AccessProtDisabled;
 *   config->endianSwapEn = kBEE_EndianSwapEnabled;
 * endcode
 *
 * param config Configuration structure for BEE peripheral.
 */
void BEE_GetDefaultConfig(bee_region_config_t *config)
{
    assert(config);

    /* Initializes the configure structure to zero. */
    (void)memset(config, 0, sizeof(*config));

    config->region0Mode       = kBEE_AesCtrMode;
    config->region1Mode       = kBEE_AesCtrMode;
    config->region0AddrOffset = 0U;
    config->region1AddrOffset = 0U;
    config->region0SecLevel   = kBEE_SecurityLevel3;
    config->region1SecLevel   = kBEE_SecurityLevel3;
    config->region1Bot        = 0U;
    config->region1Top        = 0U;
    config->accessPermission  = kBEE_AccessProtDisabled;
    config->endianSwapEn      = kBEE_EndianSwapEnabled;
}

/*!
 * brief Sets BEE configuration.
 *
 * This function sets BEE peripheral and BEE region settings accorging to given configuration structure.
 *
 * param base BEE peripheral address.
 * param config Configuration structure for BEE.
 */
void BEE_SetConfig(BEE_Type *base, const bee_region_config_t *config)
{
    uint32_t beeCtrlVal;
    bool reenable = false;

    /* Wait until BEE is in idle state */
    while (0U == (BEE_GetStatusFlags(base) & (uint32_t)kBEE_IdleFlag))
    {
    }

    /* Disable BEE before region configuration in case it is enabled. */
    if ((base->CTRL & BEE_CTRL_BEE_ENABLE_MASK) != 0U)
    {
        BEE_Disable(base);
        reenable = true;
    }

    /* Preserve CTRL bit values that are not set by this function */
    beeCtrlVal = base->CTRL & 0xFFFF0037U;

    /* Set variable according to configuration */
    beeCtrlVal |= BEE_CTRL_AC_PROT_EN(config->accessPermission) | BEE_CTRL_LITTLE_ENDIAN(config->endianSwapEn) |
                  BEE_CTRL_SECURITY_LEVEL_R0(config->region0SecLevel) | BEE_CTRL_CTRL_AES_MODE_R0(config->region0Mode) |
                  BEE_CTRL_SECURITY_LEVEL_R1(config->region1SecLevel) | BEE_CTRL_CTRL_AES_MODE_R1(config->region1Mode);

    /* Load values to registers */
    base->CTRL         = beeCtrlVal;
    base->ADDR_OFFSET0 = config->region0AddrOffset;
    base->ADDR_OFFSET1 = config->region1AddrOffset;
    base->REGION1_BOT  = config->region1Bot;
    base->REGION1_TOP  = config->region1Top;

    /* Reenable BEE if it was enabled before. */
    if (reenable)
    {
        BEE_Enable(base);
    }
}

/*!
 * brief Loads the AES key for selected region into BEE key registers.
 *
 * This function loads given AES key to BEE register for the given region.
 * The key must be 32-bit aligned and stored in little-endian format.
 *
 * Please note, that eFuse BEE_KEYx_SEL must be set accordingly to be able to load and use key loaded in BEE registers.
 * Otherwise, key cannot loaded and BEE will use key from OTPMK or SW_GP2.
 *
 * param base BEE peripheral address.
 * param region Selection of the BEE region to be configured.
 * param key AES key (in little-endian format).
 * param keySize Size of AES key.
 */
status_t BEE_SetRegionKey(BEE_Type *base, bee_region_t region, const uint8_t *key, size_t keySize)
{
    bool redisable = false;

    /* Key must be 32-bit aligned */
    if ((0U != ((uintptr_t)key & 0x3u)) || (keySize != 16U))
    {
        return kStatus_InvalidArgument;
    }

    /* Wait until BEE is in idle state */
    while (0U == (BEE_GetStatusFlags(base) & (uint32_t)kBEE_IdleFlag))
    {
    }

    /* Clear KEY_VALID bit before new key is loaded */
    base->CTRL &= ~BEE_CTRL_KEY_VALID_MASK;

    /* Write key registers, key is stored in little-endian format in memory */
    aligned_memcpy((uint32_t *)(uint32_t)&base->AES_KEY0_W0, key, keySize);

    /* Enable BEE before key configuration. */
    if (0U == (base->CTRL & BEE_CTRL_BEE_ENABLE_MASK))
    {
        BEE_Enable(base);
        redisable = true;
    }

    if (region == kBEE_Region0)
    {
        base->CTRL &= ~BEE_CTRL_KEY_REGION_SEL_MASK;
    }

    else if (region == kBEE_Region1)
    {
        base->CTRL |= BEE_CTRL_KEY_REGION_SEL_MASK;
    }

    else
    {
        return kStatus_InvalidArgument;
    }

    /* Set KEY_VALID bit to trigger key loading */
    base->CTRL |= BEE_CTRL_KEY_VALID_MASK;
    /* Wait until key is ready */
    while (0U == (base->CTRL & BEE_CTRL_KEY_VALID_MASK))
    {
    }

    /* Redisable BEE if it was disabled before this function call. */
    if (redisable)
    {
        BEE_Disable(base);
    }

    return kStatus_Success;
}

/*!
 * brief Loads the nonce for selected region into BEE nonce registers.
 *
 * This function loads given nonce(only AES CTR mode) to BEE register for the given region.
 * The nonce must be 32-bit aligned and stored in little-endian format.
 *
 * param base BEE peripheral address.
 * param region Selection of the BEE region to be configured.
 * param nonce AES nonce (in little-endian format).
 * param nonceSize Size of AES nonce.
 */
status_t BEE_SetRegionNonce(BEE_Type *base, bee_region_t region, const uint8_t *nonce, size_t nonceSize)
{
    /* Nonce must be 32-bit aligned */
    if ((0U != ((uintptr_t)nonce & 0x3u)) || (nonceSize != 16U))
    {
        return kStatus_InvalidArgument;
    }

    /* Wait until BEE is in idle state */
    while (0U == (BEE_GetStatusFlags(base) & (uint32_t)kBEE_IdleFlag))
    {
    }

    /* Write nonce registers, nonce is stored in little-endian format in memory */
    if (region == kBEE_Region0)
    {
        aligned_memcpy((uint32_t *)(uint32_t)&base->CTR_NONCE0_W0, nonce, nonceSize);
    }

    else if (region == kBEE_Region1)
    {
        aligned_memcpy((uint32_t *)(uint32_t)&base->CTR_NONCE1_W0, nonce, nonceSize);
    }

    else
    {
        return kStatus_InvalidArgument;
    }

    return kStatus_Success;
}

/*!
 * brief Gets the BEE status flags.
 *
 * This function returns status of BEE peripheral.
 *
 * param base BEE peripheral address.
 *
 * return The status flags. This is the logical OR of members of the
 *         enumeration ::bee_status_flags_t
 */
uint32_t BEE_GetStatusFlags(BEE_Type *base)
{
    return base->STATUS;
}

/*!
 * brief Clears the BEE status flags.
 *
 * param base BEE peripheral base address.
 * param mask The status flags to clear. This is a logical OR of members of the
 *             enumeration ::bee_status_flags_t
 */
void BEE_ClearStatusFlags(BEE_Type *base, uint32_t mask)
{
    /* w1c */
    base->STATUS |= mask;
}
