/*
 * Copyright (c) 2015-2016, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "fsl_ltc.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
/*! Full word representing the actual bit values for the LTC mode register. */
typedef uint32_t ltc_mode_t;

#define LTC_FIFO_SZ_MAX_DOWN_ALGN (0xff0u)
#define LTC_MD_ALG_AES (0x10U)        /*!< Bit field value for LTC_MD_ALG: AES */
#define LTC_MD_ALG_DES (0x20U)        /*!< Bit field value for LTC_MD_ALG: DES */
#define LTC_MD_ALG_TRIPLE_DES (0x21U) /*!< Bit field value for LTC_MD_ALG: 3DES */
#define LTC_MD_ALG_SHA1 (0x41U)       /*!< Bit field value for LTC_MD_ALG: SHA-1 */
#define LTC_MD_ALG_SHA224 (0x42U)     /*!< Bit field value for LTC_MD_ALG: SHA-224 */
#define LTC_MD_ALG_SHA256 (0x43U)     /*!< Bit field value for LTC_MD_ALG: SHA-256 */
#define LTC_MDPK_ALG_PKHA (0x80U)     /*!< Bit field value for LTC_MDPK_ALG: PKHA */
#define LTC_MD_ENC_DECRYPT (0U)       /*!< Bit field value for LTC_MD_ENC: Decrypt. */
#define LTC_MD_ENC_ENCRYPT (0x1U)     /*!< Bit field value for LTC_MD_ENC: Encrypt. */
#define LTC_MD_AS_UPDATE (0U)         /*!< Bit field value for LTC_MD_AS: Update */
#define LTC_MD_AS_INITIALIZE (0x1U)   /*!< Bit field value for LTC_MD_AS: Initialize */
#define LTC_MD_AS_FINALIZE (0x2U)     /*!< Bit field value for LTC_MD_AS: Finalize */
#define LTC_MD_AS_INIT_FINAL (0x3U)   /*!< Bit field value for LTC_MD_AS: Initialize/Finalize */

#define LTC_AES_GCM_TYPE_AAD 55
#define LTC_AES_GCM_TYPE_IV 0

#define LTC_CCM_TAG_IDX 8 /*! For CCM encryption, the encrypted final MAC is written to the context word 8-11 */
#define LTC_GCM_TAG_IDX 0 /*! For GCM encryption, the encrypted final MAC is written to the context word 0-3 */

enum _ltc_md_dk_bit_shift
{
    kLTC_ModeRegBitShiftDK = 12U,
};

typedef enum _ltc_algorithm
{
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
    kLTC_AlgorithmPKHA = LTC_MDPK_ALG_PKHA << LTC_MD_ALG_SHIFT,
#endif /* FSL_FEATURE_LTC_HAS_PKHA */
    kLTC_AlgorithmAES = LTC_MD_ALG_AES << LTC_MD_ALG_SHIFT,
#if defined(FSL_FEATURE_LTC_HAS_DES) && FSL_FEATURE_LTC_HAS_DES
    kLTC_AlgorithmDES = LTC_MD_ALG_DES << LTC_MD_ALG_SHIFT,
    kLTC_Algorithm3DES = LTC_MD_ALG_TRIPLE_DES << LTC_MD_ALG_SHIFT,
#endif /* FSL_FEATURE_LTC_HAS_DES */
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
    kLTC_AlgorithmSHA1 = LTC_MD_ALG_SHA1 << LTC_MD_ALG_SHIFT,
    kLTC_AlgorithmSHA224 = LTC_MD_ALG_SHA224 << LTC_MD_ALG_SHIFT,
    kLTC_AlgorithmSHA256 = LTC_MD_ALG_SHA256 << LTC_MD_ALG_SHIFT,
#endif /* FSL_FEATURE_LTC_HAS_SHA */
} ltc_algorithm_t;

typedef enum _ltc_mode_symmetric_alg
{
    kLTC_ModeCTR = 0x00U << LTC_MD_AAI_SHIFT,
    kLTC_ModeCBC = 0x10U << LTC_MD_AAI_SHIFT,
    kLTC_ModeECB = 0x20U << LTC_MD_AAI_SHIFT,
    kLTC_ModeCFB = 0x30U << LTC_MD_AAI_SHIFT,
    kLTC_ModeOFB = 0x40U << LTC_MD_AAI_SHIFT,
    kLTC_ModeCMAC = 0x60U << LTC_MD_AAI_SHIFT,
    kLTC_ModeXCBCMAC = 0x70U << LTC_MD_AAI_SHIFT,
    kLTC_ModeCCM = 0x80U << LTC_MD_AAI_SHIFT,
    kLTC_ModeGCM = 0x90U << LTC_MD_AAI_SHIFT,
} ltc_mode_symmetric_alg_t;

typedef enum _ltc_mode_encrypt
{
    kLTC_ModeDecrypt = LTC_MD_ENC_DECRYPT << LTC_MD_ENC_SHIFT,
    kLTC_ModeEncrypt = LTC_MD_ENC_ENCRYPT << LTC_MD_ENC_SHIFT,
} ltc_mode_encrypt_t;

typedef enum _ltc_mode_algorithm_state
{
    kLTC_ModeUpdate = LTC_MD_AS_UPDATE << LTC_MD_AS_SHIFT,
    kLTC_ModeInit = LTC_MD_AS_INITIALIZE << LTC_MD_AS_SHIFT,
    kLTC_ModeFinalize = LTC_MD_AS_FINALIZE << LTC_MD_AS_SHIFT,
    kLTC_ModeInitFinal = LTC_MD_AS_INIT_FINAL << LTC_MD_AS_SHIFT
} ltc_mode_algorithm_state_t;

/*! @brief LTC status flags */
enum _ltc_status_flag
{
    kLTC_StatusAesBusy = 1U << LTC_STA_AB_SHIFT,
#if defined(FSL_FEATURE_LTC_HAS_DES) && FSL_FEATURE_LTC_HAS_DES
    kLTC_StatusDesBusy = 1U << LTC_STA_DB_SHIFT,
#endif /* FSL_FEATURE_LTC_HAS_DES */
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
    kLTC_StatusPkhaBusy = 1U << LTC_STA_PB_SHIFT,
#endif /* FSL_FEATURE_LTC_HAS_PKHA */
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
    kLTC_StatusMdhaBusy = 1U << LTC_STA_MB_SHIFT,
#endif /* FSL_FEATURE_LTC_HAS_SHA */
    kLTC_StatusDoneIsr = 1U << LTC_STA_DI_SHIFT,
    kLTC_StatusErrorIsr = 1U << LTC_STA_EI_SHIFT,
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
    kLTC_StatusPublicKeyPrime = 1U << LTC_STA_PKP_SHIFT,
    kLTC_StatusPublicKeyOpOne = 1U << LTC_STA_PKO_SHIFT,
    kLTC_StatusPublicKeyOpZero = 1U << LTC_STA_PKZ_SHIFT,
#endif /* FSL_FEATURE_LTC_HAS_PKHA */
    kLTC_StatusAll = LTC_STA_AB_MASK |
#if defined(FSL_FEATURE_LTC_HAS_DES) && FSL_FEATURE_LTC_HAS_DES
                     LTC_STA_DB_MASK |
#endif /* FSL_FEATURE_LTC_HAS_DES */
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
                     LTC_STA_MB_MASK |
#endif /* FSL_FEATURE_LTC_HAS_SHA */
                     LTC_STA_DI_MASK | LTC_STA_EI_MASK
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
                     |
                     LTC_STA_PB_MASK | LTC_STA_PKP_MASK | LTC_STA_PKO_MASK | LTC_STA_PKZ_MASK
#endif /* FSL_FEATURE_LTC_HAS_PKHA */
};

/*! @brief LTC clear register */
typedef enum _ltc_clear_written
{
    kLTC_ClearMode = 1U << LTC_CW_CM_SHIFT,
    kLTC_ClearDataSize = 1U << LTC_CW_CDS_SHIFT,
    kLTC_ClearIcvSize = 1U << LTC_CW_CICV_SHIFT,
    kLTC_ClearContext = 1U << LTC_CW_CCR_SHIFT,
    kLTC_ClearKey = 1U << LTC_CW_CKR_SHIFT,
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
    kLTC_ClearPkhaSizeA = 1U << LTC_CW_CPKA_SHIFT,
    kLTC_ClearPkhaSizeB = 1U << LTC_CW_CPKB_SHIFT,
    kLTC_ClearPkhaSizeN = 1U << LTC_CW_CPKN_SHIFT,
    kLTC_ClearPkhaSizeE = 1U << LTC_CW_CPKE_SHIFT,
    kLTC_ClearAllSize = (int)kLTC_ClearPkhaSizeA | kLTC_ClearPkhaSizeB | kLTC_ClearPkhaSizeN | kLTC_ClearPkhaSizeE,
#endif /* FSL_FEATURE_LTC_HAS_PKHA */
    kLTC_ClearOutputFifo = 1U << LTC_CW_COF_SHIFT,
    kLTC_ClearInputFifo = (int)(1U << LTC_CW_CIF_SHIFT),
    kLTC_ClearAll = (int)(LTC_CW_CM_MASK | LTC_CW_CDS_MASK | LTC_CW_CICV_MASK | LTC_CW_CCR_MASK | LTC_CW_CKR_MASK |
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
                          LTC_CW_CPKA_MASK | LTC_CW_CPKB_MASK | LTC_CW_CPKN_MASK | LTC_CW_CPKE_MASK |
#endif /* FSL_FEATURE_LTC_HAS_PKHA */
                          LTC_CW_COF_MASK | LTC_CW_CIF_MASK)
} ltc_clear_written_t;

enum _ltc_ctrl_swap
{
    kLTC_CtrlSwapAll =
        LTC_CTL_IFS_MASK | LTC_CTL_OFS_MASK | LTC_CTL_KIS_MASK | LTC_CTL_KOS_MASK | LTC_CTL_CIS_MASK | LTC_CTL_COS_MASK,
};

/*! @brief Type used in GCM and CCM modes.

    Content of a block is established via individual bytes and moved to LTC
   IFIFO by moving 32-bit words.
*/
typedef union _ltc_xcm_block_t
{
    uint32_t w[4]; /*!< LTC context register is 16 bytes written as four 32-bit words */
    uint8_t b[16]; /*!< 16 octets block for CCM B0 and CTR0 and for GCM */
} ltc_xcm_block_t;

#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA

/*! @brief PKHA functions - arithmetic, copy/clear memory. */
typedef enum _ltc_pkha_func_t
{
    kLTC_PKHA_ClearMem = 1U,
    kLTC_PKHA_ArithModAdd = 2U,         /*!< (A + B) mod N */
    kLTC_PKHA_ArithModSub1 = 3U,        /*!< (A - B) mod N */
    kLTC_PKHA_ArithModSub2 = 4U,        /*!< (B - A) mod N */
    kLTC_PKHA_ArithModMul = 5U,         /*!< (A x B) mod N */
    kLTC_PKHA_ArithModExp = 6U,         /*!< (A^E) mod N */
    kLTC_PKHA_ArithModRed = 7U,         /*!< (A) mod N */
    kLTC_PKHA_ArithModInv = 8U,         /*!< (A^-1) mod N */
    kLTC_PKHA_ArithEccAdd = 9U,         /*!< (P1 + P2) */
    kLTC_PKHA_ArithEccDouble = 10U,     /*!< (P2 + P2) */
    kLTC_PKHA_ArithEccMul = 11U,        /*!< (E x P1) */
    kLTC_PKHA_ArithModR2 = 12U,         /*!< (R^2 mod N) */
    kLTC_PKHA_ArithGcd = 14U,           /*!< GCD (A, N) */
    kLTC_PKHA_ArithPrimalityTest = 15U, /*!< Miller-Rabin */
    kLTC_PKHA_CopyMemSizeN = 16U,
    kLTC_PKHA_CopyMemSizeSrc = 17U,
} ltc_pkha_func_t;

/*! @brief Register areas for PKHA clear memory operations. */
typedef enum _ltc_pkha_reg_area
{
    kLTC_PKHA_RegA = 8U,
    kLTC_PKHA_RegB = 4U,
    kLTC_PKHA_RegE = 2U,
    kLTC_PKHA_RegN = 1U,
    kLTC_PKHA_RegAll = kLTC_PKHA_RegA | kLTC_PKHA_RegB | kLTC_PKHA_RegE | kLTC_PKHA_RegN,
} ltc_pkha_reg_area_t;

/*! @brief Quadrant areas for 2048-bit registers for PKHA copy memory
 * operations. */
typedef enum _ltc_pkha_quad_area_t
{
    kLTC_PKHA_Quad0 = 0U,
    kLTC_PKHA_Quad1 = 1U,
    kLTC_PKHA_Quad2 = 2U,
    kLTC_PKHA_Quad3 = 3U,
} ltc_pkha_quad_area_t;

/*! @brief User-supplied (R^2 mod N) input or LTC should calculate. */
typedef enum _ltc_pkha_r2_t
{
    kLTC_PKHA_CalcR2 = 0U, /*!< Calculate (R^2 mod N) */
    kLTC_PKHA_InputR2 = 1U /*!< (R^2 mod N) supplied as input */
} ltc_pkha_r2_t;

/*! @brief LTC PKHA parameters */
typedef struct _ltc_pkha_mode_params_t
{
    ltc_pkha_func_t func;
    ltc_pkha_f2m_t arithType;
    ltc_pkha_montgomery_form_t montFormIn;
    ltc_pkha_montgomery_form_t montFormOut;
    ltc_pkha_reg_area_t srcReg;
    ltc_pkha_quad_area_t srcQuad;
    ltc_pkha_reg_area_t dstReg;
    ltc_pkha_quad_area_t dstQuad;
    ltc_pkha_timing_t equalTime;
    ltc_pkha_r2_t r2modn;
} ltc_pkha_mode_params_t;

#endif /* FSL_FEATURE_LTC_HAS_PKHA */

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
static status_t ltc_pkha_clear_regabne(LTC_Type *base, bool A, bool B, bool N, bool E);
#endif /* FSL_FEATURE_LTC_HAS_PKHA */

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

/*******************************************************************************
 * LTC Common code static
 ******************************************************************************/
/*!
 * @brief Tests the correct key size.
 *
 * This function tests the correct key size.
 * @param keySize Input key length in bytes.
 * @return True if the key length is supported, false if not.
 */
bool ltc_check_key_size(const uint32_t keySize)
{
    return ((keySize == 16u)
#if defined(FSL_FEATURE_LTC_HAS_AES192) && FSL_FEATURE_LTC_HAS_AES192
            || ((keySize == 24u))
#endif /* FSL_FEATURE_LTC_HAS_AES192 */
#if defined(FSL_FEATURE_LTC_HAS_AES256) && FSL_FEATURE_LTC_HAS_AES256
            || ((keySize == 32u))
#endif /* FSL_FEATURE_LTC_HAS_AES256 */
                );
}

/*! @brief LTC driver wait mechanism. */
status_t ltc_wait(LTC_Type *base)
{
    status_t status;

    bool error = false;
    bool done = false;

    /* Wait for 'done' or 'error' flag. */
    while ((!error) && (!done))
    {
        uint32_t temp32 = base->STA;
        error = temp32 & LTC_STA_EI_MASK;
        done = temp32 & LTC_STA_DI_MASK;
    }

    if (error)
    {
        base->COM = LTC_COM_ALL_MASK; /* Reset all engine to clear the error flag */
        status = kStatus_Fail;
    }
    else /* 'done' */
    {
        status = kStatus_Success;

        base->CW = kLTC_ClearDataSize;
        /* Clear 'done' interrupt status.  This also clears the mode register. */
        base->STA = kLTC_StatusDoneIsr;
    }

    return status;
}

/*!
 * @brief Clears the LTC module.
 * This function can be used to clear all sensitive data from theLTC module, such as private keys. It is called
 * internally by the LTC driver in case of an error or operation complete.
 * @param base LTC peripheral base address
 * @param pkha Include LTC PKHA register clear. If there is no PKHA, the argument is ignored.
 */
void ltc_clear_all(LTC_Type *base, bool addPKHA)
{
    base->CW = (uint32_t)kLTC_ClearAll;
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
    if (addPKHA)
    {
        ltc_pkha_clear_regabne(base, true, true, true, true);
    }
#endif /* FSL_FEATURE_LTC_HAS_PKHA */
}

void ltc_memcpy(void *dst, const void *src, size_t size)
{
#if defined(__cplusplus)
    register uint8_t *to = (uint8_t *)dst;
    register const uint8_t *from = (const uint8_t *)src;
#else
    register uint8_t *to = dst;
    register const uint8_t *from = src;
#endif
    while (size)
    {
        *to = *from;
        size--;
        to++;
        from++;
    }
}

/*!
 * @brief Reads an unaligned word.
 *
 * This function creates a 32-bit word from an input array of four bytes.
 *
 * @param src Input array of four bytes. The array can start at any address in memory.
 * @return 32-bit unsigned int created from the input byte array.
 */
static inline uint32_t ltc_get_word_from_unaligned(const uint8_t *srcAddr)
{
#if (!(defined(__CORTEX_M)) || (defined(__CORTEX_M) && (__CORTEX_M == 0)))
    register const uint8_t *src = srcAddr;
    /* Cortex M0 does not support misaligned loads */
    if ((uint32_t)src & 0x3u)
    {
        union _align_bytes_t
        {
            uint32_t word;
            uint8_t byte[sizeof(uint32_t)];
        } my_bytes;

        my_bytes.byte[0] = *src;
        my_bytes.byte[1] = *(src + 1);
        my_bytes.byte[2] = *(src + 2);
        my_bytes.byte[3] = *(src + 3);
        return my_bytes.word;
    }
    else
    {
        /* addr aligned to 0-modulo-4 so it is safe to type cast */
        return *((const uint32_t *)src);
    }
#elif defined(__CC_ARM)
    /* -O3 optimization in Keil 5.15 and 5.16a uses LDM instruction here (LDM r4!, {r0})
     *    which is wrong, because srcAddr might be unaligned.
     *    LDM on unaligned address causes hard-fault. in contrary,
     *    LDR supports unaligned address on Cortex M4 */
    register uint32_t retVal;
    __asm
    {
        LDR retVal, [srcAddr]
    }
    return retVal;
#else
    return *((const uint32_t *)srcAddr);
#endif
}

/*!
 * @brief Converts a 32-bit word into a byte array.
 *
 * This function creates an output array of four bytes from an input 32-bit word.
 *
 * @param srcWord Input 32-bit unsigned integer.
 * @param dst Output array of four bytes. The array can start at any address in memory.
 */
static inline void ltc_set_unaligned_from_word(uint32_t srcWord, uint8_t *dstAddr)
{
#if (!(defined(__CORTEX_M)) || (defined(__CORTEX_M) && (__CORTEX_M == 0)))
    register uint8_t *dst = dstAddr;
    /* Cortex M0 does not support misaligned stores */
    if ((uint32_t)dst & 0x3u)
    {
        *dst++ = (srcWord & 0x000000FFU);
        *dst++ = (srcWord & 0x0000FF00U) >> 8;
        *dst++ = (srcWord & 0x00FF0000U) >> 16;
        *dst++ = (srcWord & 0xFF000000U) >> 24;
    }
    else
    {
        *((uint32_t *)dstAddr) = srcWord; /* addr aligned to 0-modulo-4 so it is safe to type cast */
    }
#elif defined(__CC_ARM)
    __asm
    {
        STR srcWord, [dstAddr]
    }
    return;
#else
    *((uint32_t *)dstAddr) = srcWord;
#endif
}

/*!
 * @brief Sets the LTC keys.
 *
 * This function writes the LTC keys into the key register.  The keys should
 * be written before the key size.
 *
 * @param base LTC peripheral base address
 * @param key Key
 * @param keySize Number of bytes for all keys to be loaded (maximum 32, must be a
 *                multiple of 4).
 * @returns Key set status
 */
static status_t ltc_set_key(LTC_Type *base, const uint8_t *key, uint8_t keySize)
{
    int32_t i;

    for (i = 0; i < (keySize / 4); i++)
    {
        base->KEY[i] = ltc_get_word_from_unaligned(key + i * sizeof(uint32_t));
    }

    return kStatus_Success;
}

/*!
 * @brief Gets the LTC keys.
 *
 * This function retrieves the LTC keys from the key register.
 *
 * @param base LTC peripheral base address
 * @param key Array of data to store keys
 * @param keySize Number of bytes of keys to retrieve
 * @returns Key set status
 */
static status_t ltc_get_key(LTC_Type *base, uint8_t *key, uint8_t keySize)
{
    int32_t i;

    for (i = 0; i < (keySize / 4); i++)
    {
        ltc_set_unaligned_from_word(base->KEY[i], key + i * sizeof(uint32_t));
    }

    return kStatus_Success;
}

/*!
 * @brief Writes the LTC context register;
 *
 * The LTC context register is a 512 bit (64 byte) register that holds
 * internal context for the crypto engine.  The meaning varies based on the
 * algorithm and operating state being used.  This register is written by the
 * driver/application to load state such as IV, counter, and so on. Then, it is
 * updated by the internal crypto engine as needed.
 *
 * @param base LTC peripheral base address
 * @param data Data to write
 * @param dataSize Size of data to write in bytes
 * @param startIndex Starting word (4-byte) index into the 16-word register.
 * @return Status of write
 */
status_t ltc_set_context(LTC_Type *base, const uint8_t *data, uint8_t dataSize, uint8_t startIndex)
{
    int32_t i;
    int32_t j;
    int32_t szLeft;

    /* Context register is 16 words in size (64 bytes).  Ensure we are only
     * writing a valid amount of data. */
    if (startIndex + (dataSize / 4) >= 16)
    {
        return kStatus_InvalidArgument;
    }

    j = 0;
    szLeft = dataSize % 4;
    for (i = startIndex; i < (startIndex + dataSize / 4); i++)
    {
        base->CTX[i] = ltc_get_word_from_unaligned(data + j);
        j += sizeof(uint32_t);
    }

    if (szLeft)
    {
        uint32_t context_data = {0};
        ltc_memcpy(&context_data, data + j, szLeft);
        base->CTX[i] = context_data;
    }
    return kStatus_Success;
}

/*!
 * @brief Reads the LTC context register.
 *
 * The LTC context register is a 512 bit (64 byte) register that holds
 * internal context for the crypto engine.  The meaning varies based on the
 * algorithm and operating state being used.  This register is written by the
 * driver/application to load state such as IV, counter, and so on. Then, it is
 * updated by the internal crypto engine as needed.
 *
 * @param base LTC peripheral base address
 * @param data Destination of read data
 * @param dataSize Size of data to read in bytes
 * @param startIndex Starting word (4-byte) index into the 16-word register.
 * @return Status of read
 */
status_t ltc_get_context(LTC_Type *base, uint8_t *dest, uint8_t dataSize, uint8_t startIndex)
{
    int32_t i;
    int32_t j;
    int32_t szLeft;
    uint32_t rdCtx;

    /* Context register is 16 words in size (64 bytes).  Ensure we are only
     * writing a valid amount of data. */
    if (startIndex + (dataSize / 4) >= 16)
    {
        return kStatus_InvalidArgument;
    }

    j = 0;
    szLeft = dataSize % 4;
    for (i = startIndex; i < (startIndex + dataSize / 4); i++)
    {
        ltc_set_unaligned_from_word(base->CTX[i], dest + j);
        j += sizeof(uint32_t);
    }

    if (szLeft)
    {
        rdCtx = 0;
        rdCtx = base->CTX[i];
        ltc_memcpy(dest + j, &rdCtx, szLeft);
    }
    return kStatus_Success;
}

static status_t ltc_symmetric_alg_state(LTC_Type *base,
                                        const uint8_t *key,
                                        uint8_t keySize,
                                        ltc_algorithm_t alg,
                                        ltc_mode_symmetric_alg_t mode,
                                        ltc_mode_encrypt_t enc,
                                        ltc_mode_algorithm_state_t as)
{
    ltc_mode_t modeReg;

    /* Clear internal register states. */
    base->CW = (uint32_t)kLTC_ClearAll;

    /* Set byte swap on for several registers we will be reading and writing
     * user data to/from. */
    base->CTL |= kLTC_CtrlSwapAll;

    /* Write the key in place. */
    ltc_set_key(base, key, keySize);

    /* Write the key size.  This must be done after writing the key, and this
     * action locks the ability to modify the key registers. */
    base->KS = keySize;

    /* Clear the 'done' interrupt. */
    base->STA = kLTC_StatusDoneIsr;

    /* Set the proper block and algorithm mode. */
    modeReg = (uint32_t)alg | (uint32_t)enc | (uint32_t)as | (uint32_t)mode;

    /* Write the mode register to the hardware. */
    base->MD = modeReg;

    return kStatus_Success;
}

/*!
 * @brief Initializes the LTC for symmetric encrypt/decrypt operation. Mode is set to UPDATE.
 *
 * @param base LTC peripheral base address
 * @param key Input key to use for encryption
 * @param keySize Size of the input key, in bytes. Must be 8, 16, 24, or 32.
 * @param alg Symmetric algorithm
 * @param mode Symmetric block mode
 * @param enc Encrypt/decrypt control
 * @return Status
 */
status_t ltc_symmetric_update(LTC_Type *base,
                              const uint8_t *key,
                              uint8_t keySize,
                              ltc_algorithm_t alg,
                              ltc_mode_symmetric_alg_t mode,
                              ltc_mode_encrypt_t enc)
{
    return ltc_symmetric_alg_state(base, key, keySize, alg, mode, enc, kLTC_ModeUpdate);
}

#if defined(FSL_FEATURE_LTC_HAS_GCM) && FSL_FEATURE_LTC_HAS_GCM
/*!
 * @brief Initializes the LTC for symmetric encrypt/decrypt operation. Mode is set to FINALIZE.
 *
 * @param base LTC peripheral base address
 * @param key Input key to use for encryption
 * @param keySize Size of the input key, in bytes. Must be 8, 16, 24, or 32.
 * @param alg Symmetric algorithm
 * @param mode Symmetric block mode
 * @param enc Encrypt/decrypt control
 * @return Status
 */
static status_t ltc_symmetric_final(LTC_Type *base,
                                    const uint8_t *key,
                                    uint8_t keySize,
                                    ltc_algorithm_t alg,
                                    ltc_mode_symmetric_alg_t mode,
                                    ltc_mode_encrypt_t enc)
{
    return ltc_symmetric_alg_state(base, key, keySize, alg, mode, enc, kLTC_ModeFinalize);
}
#endif /* FSL_FEATURE_LTC_HAS_GCM */

/*!
 * @brief Initializes the LTC for symmetric encrypt/decrypt operation. Mode is set to INITIALIZE.
 *
 * @param base LTC peripheral base address
 * @param key Input key to use for encryption
 * @param keySize Size of the input key, in bytes. Must be 8, 16, 24, or 32.
 * @param alg Symmetric algorithm
 * @param mode Symmetric block mode
 * @param enc Encrypt/decrypt control
 * @return Status
 */
static status_t ltc_symmetric_init(LTC_Type *base,
                                   const uint8_t *key,
                                   uint8_t keySize,
                                   ltc_algorithm_t alg,
                                   ltc_mode_symmetric_alg_t mode,
                                   ltc_mode_encrypt_t enc)
{
    return ltc_symmetric_alg_state(base, key, keySize, alg, mode, enc, kLTC_ModeInit);
}

/*!
 * @brief Initializes the LTC for symmetric encrypt/decrypt operation. Mode is set to INITIALIZE/FINALIZE.
 *
 * @param base LTC peripheral base address
 * @param key Input key to use for encryption
 * @param keySize Size of the input key, in bytes. Must be 8, 16, 24, or 32.
 * @param alg Symmetric algorithm
 * @param mode Symmetric block mode
 * @param enc Encrypt/decrypt control
 * @return Status
 */
static status_t ltc_symmetric_init_final(LTC_Type *base,
                                         const uint8_t *key,
                                         uint8_t keySize,
                                         ltc_algorithm_t alg,
                                         ltc_mode_symmetric_alg_t mode,
                                         ltc_mode_encrypt_t enc)
{
    return ltc_symmetric_alg_state(base, key, keySize, alg, mode, enc, kLTC_ModeInitFinal);
}

void ltc_symmetric_process(LTC_Type *base, uint32_t inSize, const uint8_t **inData, uint8_t **outData)
{
    uint32_t outSize;
    uint32_t fifoData;
    uint32_t fifoStatus;

    register const uint8_t *in = *inData;
    register uint8_t *out = *outData;

    outSize = inSize;
    while ((outSize > 0) || (inSize > 0))
    {
        fifoStatus = base->FIFOSTA;

        /* Check output FIFO level to make sure there is at least an entry
         * ready to be read. */
        if (fifoStatus & LTC_FIFOSTA_OFL_MASK)
        {
            /* Read data from the output FIFO. */
            if (outSize > 0)
            {
                if (outSize >= sizeof(uint32_t))
                {
                    ltc_set_unaligned_from_word(base->OFIFO, out);
                    out += sizeof(uint32_t);
                    outSize -= sizeof(uint32_t);
                }
                else /* (outSize > 0) && (outSize < 4) */
                {
                    fifoData = base->OFIFO;
                    ltc_memcpy(out, &fifoData, outSize);
                    out += outSize;
                    outSize = 0;
                }
            }
        }

        /* Check input FIFO status to see if it is full.  We can
         * only write more data when both input and output FIFOs are not at a full state.
         * At the same time we are sure Output FIFO is not full because we have poped at least one entry
         * by the while loop above.
         */
        if (!(fifoStatus & LTC_FIFOSTA_IFF_MASK))
        {
            /* Copy data to the input FIFO.
             * Data can only be copied one word at a time, so pad the data
             * appropriately if it is less than this size. */
            if (inSize > 0)
            {
                if (inSize >= sizeof(uint32_t))
                {
                    base->IFIFO = ltc_get_word_from_unaligned(in);
                    inSize -= sizeof(uint32_t);
                    in += sizeof(uint32_t);
                }
                else /* (inSize > 0) && (inSize < 4) */
                {
                    fifoData = 0;
                    ltc_memcpy(&fifoData, in, inSize);
                    base->IFIFO = fifoData;
                    in += inSize;
                    inSize = 0;
                }
            }
        }
    }
    *inData = in;
    *outData = out;
}

/*!
 * @brief Processes symmetric data through LTC AES and DES engines.
 *
 * @param base LTC peripheral base address
 * @param inData Input data
 * @param inSize Size of input data, in bytes
 * @param outData Output data
 * @return Status from encrypt/decrypt operation
 */
status_t ltc_symmetric_process_data(LTC_Type *base, const uint8_t *inData, uint32_t inSize, uint8_t *outData)
{
    uint32_t lastSize;

    if ((!inData) || (!outData))
    {
        return kStatus_InvalidArgument;
    }

    /* Write the data size. */
    base->DS = inSize;

    /* Split the inSize into full 16-byte chunks and last incomplete block due to LTC AES OFIFO errata */
    if (inSize <= 16u)
    {
        lastSize = inSize;
        inSize = 0;
    }
    else
    {
        /* Process all 16-byte data chunks. */
        lastSize = inSize % 16u;
        if (lastSize == 0)
        {
            lastSize = 16;
            inSize -= 16;
        }
        else
        {
            inSize -= lastSize; /* inSize will be rounded down to 16 byte boundary. remaining bytes in lastSize */
        }
    }

    ltc_symmetric_process(base, inSize, &inData, &outData);
    ltc_symmetric_process(base, lastSize, &inData, &outData);
    return ltc_wait(base);
}

/*!
 * @brief Splits the LTC job into sessions. Used for CBC, CTR, CFB, OFB cipher block modes.
 *
 * @param base LTC peripheral base address
 * @param inData Input data to process.
 * @param inSize Input size of the input buffer.
 * @param outData Output data buffer.
 */
static status_t ltc_process_message_in_sessions(LTC_Type *base,
                                                const uint8_t *inData,
                                                uint32_t inSize,
                                                uint8_t *outData)
{
    uint32_t sz;
    status_t retval;
    ltc_mode_t modeReg; /* read and write LTC mode register */

    sz = LTC_FIFO_SZ_MAX_DOWN_ALGN;
    modeReg = base->MD;
    retval = kStatus_Success;

    while (inSize)
    {
        if (inSize <= sz)
        {
            retval = ltc_symmetric_process_data(base, inData, inSize, outData);
            if (kStatus_Success != retval)
            {
                return retval;
            }
            inSize = 0;
        }
        else
        {
            retval = ltc_symmetric_process_data(base, inData, sz, outData);
            if (kStatus_Success != retval)
            {
                return retval;
            }
            inData += sz;
            inSize -= sz;
            outData += sz;
            base->MD = modeReg;
        }
    }
    return retval;
}

static void ltc_move_block_to_ififo(LTC_Type *base, const ltc_xcm_block_t *blk, uint32_t num_bytes)
{
    uint32_t i = 0;
    uint32_t words;

    words = num_bytes / 4u;
    if (num_bytes % 4u)
    {
        words++;
    }

    if (words > 4)
    {
        words = 4;
    }

    while (i < words)
    {
        if (0U == (base->FIFOSTA & LTC_FIFOSTA_IFF_MASK))
        {
            /* Copy data to the input FIFO. */
            base->IFIFO = blk->w[i++];
        }
    }
}

static void ltc_move_to_ififo(LTC_Type *base, const uint8_t *data, uint32_t dataSize)
{
    ltc_xcm_block_t blk;
    ltc_xcm_block_t blkZero = {{0x0u, 0x0u, 0x0u, 0x0u}};

    while (dataSize)
    {
        if (dataSize > 16u)
        {
            ltc_memcpy(&blk, data, 16u);
            dataSize -= 16u;
            data += 16u;
        }
        else
        {
            ltc_memcpy(&blk, &blkZero, sizeof(ltc_xcm_block_t)); /* memset blk to zeroes */
            ltc_memcpy(&blk, data, dataSize);
            dataSize = 0;
        }
        ltc_move_block_to_ififo(base, &blk, sizeof(ltc_xcm_block_t));
    }
}

/*!
 * @brief Processes symmetric data through LTC AES in multiple sessions.
 *
 * Specific for AES CCM and GCM modes as they need to update mode register.
 *
 * @param base LTC peripheral base address
 * @param inData Input data
 * @param inSize Size of input data, in bytes
 * @param outData Output data
 * @param lastAs The LTC Algorithm state to be set sup for last block during message processing in multiple sessions.
 *               For CCM it is kLTC_ModeFinalize. For GCM it is kLTC_ModeInitFinal.
 * @return Status from encrypt/decrypt operation
 */
static status_t ltc_symmetric_process_data_multiple(LTC_Type *base,
                                                    const uint8_t *inData,
                                                    uint32_t inSize,
                                                    uint8_t *outData,
                                                    ltc_mode_t modeReg,
                                                    ltc_mode_algorithm_state_t lastAs)
{
    uint32_t fifoConsumed;
    uint32_t lastSize;
    uint32_t sz;
    uint32_t max_ltc_fifo_size;
    ltc_mode_algorithm_state_t fsm;
    status_t status;

    if ((!inData) || (!outData))
    {
        return kStatus_InvalidArgument;
    }

    if (!((kLTC_ModeFinalize == lastAs) || (kLTC_ModeInitFinal == lastAs)))
    {
        return kStatus_InvalidArgument;
    }

    if (0 == inSize)
    {
        return kStatus_Success;
    }

    if (inSize <= 16u)
    {
        fsm = lastAs;
        lastSize = inSize;
    }
    else
    {
        fsm = (ltc_mode_algorithm_state_t)(
            modeReg &
            LTC_MD_AS_MASK); /* this will be either kLTC_ModeInit or kLTC_ModeUpdate, based on prior processing */

        /* Process all 16-byte data chunks. */
        lastSize = inSize % 16u;
        if (lastSize == 0u)
        {
            lastSize = 16u;
            inSize -= 16u;
        }
        else
        {
            inSize -= lastSize; /* inSize will be rounded down to 16 byte boundary. remaining bytes in lastSize */
        }
    }

    max_ltc_fifo_size = LTC_FIFO_SZ_MAX_DOWN_ALGN;
    fifoConsumed = base->DS;

    while (lastSize)
    {
        switch (fsm)
        {
            case kLTC_ModeUpdate:
            case kLTC_ModeInit:
                while (inSize)
                {
                    if (inSize > (max_ltc_fifo_size - fifoConsumed))
                    {
                        sz = (max_ltc_fifo_size - fifoConsumed);
                    }
                    else
                    {
                        sz = inSize;
                    }
                    base->DS = sz;
                    ltc_symmetric_process(base, sz, &inData, &outData);
                    inSize -= sz;
                    fifoConsumed = 0;

                    /* after we completed INITIALIZE job, are there still any data left? */
                    if (inSize)
                    {
                        fsm = kLTC_ModeUpdate;
                        status = ltc_wait(base);
                        if (kStatus_Success != status)
                        {
                            return status;
                        }
                        modeReg &= ~LTC_MD_AS_MASK;
                        modeReg |= (uint32_t)fsm;
                        base->MD = modeReg;
                    }
                    else
                    {
                        fsm = lastAs;
                    }
                }
                break;

            case kLTC_ModeFinalize:
            case kLTC_ModeInitFinal:
                /* process last block in FINALIZE */

                status = ltc_wait(base);
                if (kStatus_Success != status)
                {
                    return status;
                }

                modeReg &= ~LTC_MD_AS_MASK;
                modeReg |= (uint32_t)lastAs;
                base->MD = modeReg;

                base->DS = lastSize;
                ltc_symmetric_process(base, lastSize, &inData, &outData);
                lastSize = 0;
                break;

            default:
                break;
        }
    }

    status = ltc_wait(base);
    return status;
}

/*!
 * @brief Receives MAC compare.
 *
 * This function is a sub-process of CCM and GCM decryption.
 * It compares received MAC with the MAC computed during decryption.
 *
 * @param base LTC peripheral base address
 * @param tag Received MAC.
 * @param tagSize Number of bytes in the received MAC.
 * @param modeReg LTC Mode Register current value. It is modified and written to LTC Mode Register.
 */
static status_t ltc_aes_received_mac_compare(LTC_Type *base, const uint8_t *tag, uint32_t tagSize, ltc_mode_t modeReg)
{
    ltc_xcm_block_t blk = {{0x0u, 0x0u, 0x0u, 0x0u}};

    base->CW = kLTC_ClearDataSize;
    base->STA = kLTC_StatusDoneIsr;

    modeReg &= ~LTC_MD_AS_MASK;
    modeReg |= (uint32_t)kLTC_ModeUpdate | LTC_MD_ICV_TEST_MASK;
    base->MD = modeReg;

    base->DS = 0u;
    base->ICVS = tagSize;
    ltc_memcpy(&blk.b[0], &tag[0], tagSize);

    ltc_move_block_to_ififo(base, &blk, tagSize);
    return ltc_wait(base);
}

/*!
 * @brief Processes tag during AES GCM and CCM.
 *
 * This function is a sub-process of CCM and GCM encryption and decryption.
 * For encryption, it writes computed MAC to the output tag.
 * For decryption, it compares the received MAC with the computed MAC.
 *
 * @param base LTC peripheral base address
 * @param[in,out] tag Output computed MAC during encryption or Input received MAC during decryption.
 * @param tagSize Size of MAC buffer in bytes.
 * @param modeReg LTC Mode Register current value. It is checked to read Enc/Dec bit.
 *                 It is modified and written to LTC Mode Register during decryption.
 * @param ctx Index to LTC context registers with computed MAC for encryption process.
 */
static status_t ltc_aes_process_tag(LTC_Type *base, uint8_t *tag, uint32_t tagSize, ltc_mode_t modeReg, uint32_t ctx)
{
    status_t status = kStatus_Success;
    if (tag)
    {
        /* For decrypt, compare received MAC with the computed MAC. */
        if (kLTC_ModeDecrypt == (modeReg & LTC_MD_ENC_MASK))
        {
            status = ltc_aes_received_mac_compare(base, tag, tagSize, modeReg);
        }
        else /* FSL_AES_GCM_TYPE_ENCRYPT */
        {
            /* For encryption, write the computed and encrypted MAC to user buffer */
            ltc_get_context(base, &tag[0], tagSize, ctx);
        }
    }
    return status;
}

/*******************************************************************************
 * LTC Common code public
 ******************************************************************************/
void LTC_Init(LTC_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* ungate clock */
    CLOCK_EnableClock(kCLOCK_Ltc0);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

void LTC_Deinit(LTC_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* gate clock */
    CLOCK_DisableClock(kCLOCK_Ltc0);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

#if defined(FSL_FEATURE_LTC_HAS_DPAMS) && FSL_FEATURE_LTC_HAS_DPAMS
void LTC_SetDpaMaskSeed(LTC_Type *base, uint32_t mask)
{
    base->DPAMS = mask;
    /* second write as workaround for DPA mask re-seed errata */
    base->DPAMS = mask;
}
#endif /* FSL_FEATURE_LTC_HAS_DPAMS */

/*******************************************************************************
 * AES Code static
 ******************************************************************************/
static status_t ltc_aes_decrypt_ecb(LTC_Type *base,
                                    const uint8_t *ciphertext,
                                    uint8_t *plaintext,
                                    uint32_t size,
                                    const uint8_t *key,
                                    uint32_t keySize,
                                    ltc_aes_key_t keyType)
{
    status_t retval;

    /* Initialize algorithm state. */
    ltc_symmetric_update(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeECB, kLTC_ModeDecrypt);

    /* set DK bit in the LTC Mode Register AAI field for directly loaded decrypt keys */
    if (keyType == kLTC_DecryptKey)
    {
        base->MD |= (1U << kLTC_ModeRegBitShiftDK);
    }

    /* Process data and return status. */
    retval = ltc_process_message_in_sessions(base, &ciphertext[0], size, &plaintext[0]);
    return retval;
}

/*******************************************************************************
 * AES Code public
 ******************************************************************************/
status_t LTC_AES_GenerateDecryptKey(LTC_Type *base, const uint8_t *encryptKey, uint8_t *decryptKey, uint32_t keySize)
{
    uint8_t plaintext[LTC_AES_BLOCK_SIZE];
    uint8_t ciphertext[LTC_AES_BLOCK_SIZE];
    status_t status;

    if (!ltc_check_key_size(keySize))
    {
        return kStatus_InvalidArgument;
    }

    /* ECB decrypt with encrypt key will convert the key in LTC context into decrypt form of the key */
    status = ltc_aes_decrypt_ecb(base, ciphertext, plaintext, LTC_AES_BLOCK_SIZE, encryptKey, keySize, kLTC_EncryptKey);
    /* now there is decrypt form of the key in the LTC context, so take it */
    ltc_get_key(base, decryptKey, keySize);

    ltc_clear_all(base, false);

    return status;
}

status_t LTC_AES_EncryptEcb(
    LTC_Type *base, const uint8_t *plaintext, uint8_t *ciphertext, uint32_t size, const uint8_t *key, uint32_t keySize)
{
    status_t retval;

    if (!ltc_check_key_size(keySize))
    {
        return kStatus_InvalidArgument;
    }
    /* ECB mode, size must be 16-byte multiple */
    if ((size < 16u) || (size % 16u))
    {
        return kStatus_InvalidArgument;
    }

    /* Initialize algorithm state. */
    ltc_symmetric_update(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeECB, kLTC_ModeEncrypt);

    /* Process data and return status. */
    retval = ltc_process_message_in_sessions(base, &plaintext[0], size, &ciphertext[0]);
    ltc_clear_all(base, false);
    return retval;
}

status_t LTC_AES_DecryptEcb(LTC_Type *base,
                            const uint8_t *ciphertext,
                            uint8_t *plaintext,
                            uint32_t size,
                            const uint8_t *key,
                            uint32_t keySize,
                            ltc_aes_key_t keyType)
{
    status_t status;

    if (!ltc_check_key_size(keySize))
    {
        return kStatus_InvalidArgument;
    }
    /* ECB mode, size must be 16-byte multiple */
    if ((size < 16u) || (size % 16u))
    {
        return kStatus_InvalidArgument;
    }

    status = ltc_aes_decrypt_ecb(base, ciphertext, plaintext, size, key, keySize, keyType);
    ltc_clear_all(base, false);
    return status;
}

status_t LTC_AES_EncryptCbc(LTC_Type *base,
                            const uint8_t *plaintext,
                            uint8_t *ciphertext,
                            uint32_t size,
                            const uint8_t iv[LTC_AES_IV_SIZE],
                            const uint8_t *key,
                            uint32_t keySize)
{
    status_t retval;

    if (!ltc_check_key_size(keySize))
    {
        return kStatus_InvalidArgument;
    }

    /* CBC mode, size must be 16-byte multiple */
    if ((size < 16u) || (size % 16u))
    {
        return kStatus_InvalidArgument;
    }

    /* Initialize algorithm state. */
    ltc_symmetric_update(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeCBC, kLTC_ModeEncrypt);

    /* Write IV data to the context register. */
    ltc_set_context(base, &iv[0], LTC_AES_IV_SIZE, 0);

    /* Process data and return status. */
    retval = ltc_process_message_in_sessions(base, &plaintext[0], size, &ciphertext[0]);
    ltc_clear_all(base, false);
    return retval;
}

status_t LTC_AES_DecryptCbc(LTC_Type *base,
                            const uint8_t *ciphertext,
                            uint8_t *plaintext,
                            uint32_t size,
                            const uint8_t iv[LTC_AES_IV_SIZE],
                            const uint8_t *key,
                            uint32_t keySize,
                            ltc_aes_key_t keyType)
{
    status_t retval;

    if (!ltc_check_key_size(keySize))
    {
        return kStatus_InvalidArgument;
    }
    /* CBC mode, size must be 16-byte multiple */
    if ((size < 16u) || (size % 16u))
    {
        return kStatus_InvalidArgument;
    }

    /* set DK bit in the LTC Mode Register AAI field for directly loaded decrypt keys */
    if (keyType == kLTC_DecryptKey)
    {
        base->MD |= (1U << kLTC_ModeRegBitShiftDK);
    }

    /* Initialize algorithm state. */
    ltc_symmetric_update(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeCBC, kLTC_ModeDecrypt);

    /* Write IV data to the context register. */
    ltc_set_context(base, &iv[0], LTC_AES_IV_SIZE, 0);

    /* Process data and return status. */
    retval = ltc_process_message_in_sessions(base, &ciphertext[0], size, &plaintext[0]);
    ltc_clear_all(base, false);
    return retval;
}

status_t LTC_AES_CryptCtr(LTC_Type *base,
                          const uint8_t *input,
                          uint8_t *output,
                          uint32_t size,
                          uint8_t counter[LTC_AES_BLOCK_SIZE],
                          const uint8_t *key,
                          uint32_t keySize,
                          uint8_t counterlast[LTC_AES_BLOCK_SIZE],
                          uint32_t *szLeft)
{
    status_t retval;
    uint32_t lastSize;

    if (!ltc_check_key_size(keySize))
    {
        return kStatus_InvalidArgument;
    }

    lastSize = 0U;
    if (counterlast != NULL)
    {
        /* Split the size into full 16-byte chunks and last incomplete block due to LTC AES OFIFO errata */
        if (size <= 16U)
        {
            lastSize = size;
            size = 0U;
        }
        else
        {
            /* Process all 16-byte data chunks. */
            lastSize = size % 16U;
            if (lastSize == 0U)
            {
                lastSize = 16U;
                size -= 16U;
            }
            else
            {
                size -= lastSize; /* size will be rounded down to 16 byte boundary. remaining bytes in lastSize */
            }
        }
    }

    /* Initialize algorithm state. */
    ltc_symmetric_update(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeCTR, kLTC_ModeEncrypt);

    /* Write initial counter data to the context register.
     * NOTE the counter values start at 4-bytes offset into the context. */
    ltc_set_context(base, &counter[0], 16U, 4U);

    /* Process data and return status. */
    retval = ltc_process_message_in_sessions(base, input, size, output);
    if (kStatus_Success != retval)
    {
        return retval;
    }

    input += size;
    output += size;

    if ((counterlast != NULL) && lastSize)
    {
        uint8_t zeroes[16] = {0};
        ltc_mode_t modeReg;

        modeReg = (uint32_t)kLTC_AlgorithmAES | (uint32_t)kLTC_ModeCTR | (uint32_t)kLTC_ModeEncrypt;
        /* Write the mode register to the hardware. */
        base->MD = modeReg | (uint32_t)kLTC_ModeFinalize;

        /* context is re-used (CTRi) */

        /* Process data and return status. */
        retval = ltc_symmetric_process_data(base, input, lastSize, output);
        if (kStatus_Success != retval)
        {
            return retval;
        }
        if (szLeft)
        {
            *szLeft = 16U - lastSize;
        }

        /* Initialize algorithm state. */
        base->MD = modeReg | (uint32_t)kLTC_ModeUpdate;

        /* context is re-used (CTRi) */

        /* Process data and return status. */
        retval = ltc_symmetric_process_data(base, zeroes, 16U, counterlast);
    }
    ltc_get_context(base, &counter[0], 16U, 4U);
    ltc_clear_all(base, false);
    return retval;
}

#if defined(FSL_FEATURE_LTC_HAS_GCM) && FSL_FEATURE_LTC_HAS_GCM
/*******************************************************************************
 * GCM Code static
 ******************************************************************************/
static status_t ltc_aes_gcm_check_input_args(LTC_Type *base,
                                             const uint8_t *src,
                                             const uint8_t *iv,
                                             const uint8_t *aad,
                                             const uint8_t *key,
                                             uint8_t *dst,
                                             uint32_t inputSize,
                                             uint32_t ivSize,
                                             uint32_t aadSize,
                                             uint32_t keySize,
                                             uint32_t tagSize)
{
    if (!base)
    {
        return kStatus_InvalidArgument;
    }

    /* tag can be NULL to skip tag processing */
    if ((!key) || (ivSize && (!iv)) || (aadSize && (!aad)) || (inputSize && ((!src) || (!dst))))
    {
        return kStatus_InvalidArgument;
    }

    /* octet length of tag (tagSize) must be element of 4,8,12,13,14,15,16 */
    if (((tagSize > 16u) || (tagSize < 12u)) && (tagSize != 4u) && (tagSize != 8u))
    {
        return kStatus_InvalidArgument;
    }

    /* check if keySize is supported */
    if (!ltc_check_key_size(keySize))
    {
        return kStatus_InvalidArgument;
    }

    /* no IV AAD DATA makes no sense */
    if (0 == (inputSize + ivSize + aadSize))
    {
        return kStatus_InvalidArgument;
    }

    return kStatus_Success;
}

/*!
 * @brief Process Wrapper for void (*pfunc)(LTC_Type*, uint32_t, bool). Sets IV Size register.
 */
static void ivsize_next(LTC_Type *base, uint32_t ivSize, bool iv_only)
{
    base->IVSZ = LTC_IVSZ_IL(iv_only) | ((ivSize)&LTC_DS_DS_MASK);
}

/*!
 * @brief Process Wrapper for void (*pfunc)(LTC_Type*, uint32_t, bool). Sets AAD Size register.
 */
static void aadsize_next(LTC_Type *base, uint32_t aadSize, bool aad_only)
{
    base->AADSZ = LTC_AADSZ_AL(aad_only) | ((aadSize)&LTC_DS_DS_MASK);
}

/*!
 * @brief Process IV or AAD string in multi-session.
 *
 * @param base LTC peripheral base address
 * @param iv IV or AAD data
 * @param ivSize Size in bytes of IV or AAD data
 * @param modeReg LTC peripheral Mode register value
 * @param iv_only IV only or AAD only flag
 * @param type selects between IV or AAD
 */
static status_t ltc_aes_gcm_process_iv_aad(
    LTC_Type *base, const uint8_t *iv, uint32_t ivSize, ltc_mode_t modeReg, bool iv_only, int type, ltc_mode_t modeLast)
{
    uint32_t sz;
    status_t retval;
    void (*next_size_func)(LTC_Type *ltcBase, uint32_t nextSize, bool authOnly);

    if ((NULL == iv) || (ivSize == 0))
    {
        return kStatus_InvalidArgument;
    }

    sz = LTC_FIFO_SZ_MAX_DOWN_ALGN;
    next_size_func = type == LTC_AES_GCM_TYPE_AAD ? aadsize_next : ivsize_next;

    while (ivSize)
    {
        if (ivSize < sz)
        {
            modeReg &= ~LTC_MD_AS_MASK;
            modeReg |= modeLast;
            base->MD = modeReg;
            next_size_func(base, ivSize, iv_only);
            ltc_move_to_ififo(base, iv, ivSize);
            ivSize = 0;
        }
        else
        {
            /* set algorithm state to UPDATE */
            modeReg &= ~LTC_MD_AS_MASK;
            modeReg |= kLTC_ModeUpdate;
            base->MD = modeReg;

            next_size_func(base, (uint16_t)sz, true);
            ltc_move_to_ififo(base, iv, sz);
            ivSize -= sz;
            iv += sz;
        }

        retval = ltc_wait(base);
        if (kStatus_Success != retval)
        {
            return retval;
        }
    } /* end while */
    return kStatus_Success;
}

static status_t ltc_aes_gcm_process(LTC_Type *base,
                                    ltc_mode_encrypt_t encryptMode,
                                    const uint8_t *src,
                                    uint32_t inputSize,
                                    const uint8_t *iv,
                                    uint32_t ivSize,
                                    const uint8_t *aad,
                                    uint32_t aadSize,
                                    const uint8_t *key,
                                    uint32_t keySize,
                                    uint8_t *dst,
                                    uint8_t *tag,
                                    uint32_t tagSize)
{
    status_t retval;          /* return value */
    uint32_t max_ltc_fifo_sz; /* maximum data size that we can put to LTC FIFO in one session. 12-bit limit. */
    ltc_mode_t modeReg;       /* read and write LTC mode register */

    bool single_ses_proc_all; /* iv, aad and src data can be processed in one session */
    bool iv_only;
    bool aad_only;

    retval = ltc_aes_gcm_check_input_args(base, src, iv, aad, key, dst, inputSize, ivSize, aadSize, keySize, tagSize);

    /* API input validation */
    if (kStatus_Success != retval)
    {
        return retval;
    }

    max_ltc_fifo_sz = LTC_DS_DS_MASK; /* 12-bit field limit */

    /*
     * Write value to LTC AADSIZE (rounded up to next 16 byte boundary)
     * plus the write value to LTC IV (rounded up to next 16 byte boundary)
     * plus the inputSize. If the result is less than max_ltc_fifo_sz
     * then all can be processed in one session FINALIZE.
     * Otherwise, we have to split into multiple session, going through UPDATE(s), INITIALIZE, UPDATE(s) and FINALIZE.
     */
    single_ses_proc_all =
        (((aadSize + 15u) & 0xfffffff0u) + ((ivSize + 15u) & 0xfffffff0u) + inputSize) <= max_ltc_fifo_sz;

    /* setup key, algorithm and set the alg.state */
    if (single_ses_proc_all)
    {
        ltc_symmetric_final(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeGCM, encryptMode);
        modeReg = base->MD;

        iv_only = (aadSize == 0) && (inputSize == 0);
        aad_only = (inputSize == 0);

        /* DS_MASK here is not a bug. IV size field can be written with more than 4-bits,
         * as the IVSZ write value, aligned to next 16 bytes boundary, is written also to the Data Size.
         * For example, I can write 22 to IVSZ, 32 will be written to Data Size and IVSZ will have value 6, which is 22
         * mod 16.
         */
        base->IVSZ = LTC_IVSZ_IL(iv_only) | ((ivSize)&LTC_DS_DS_MASK);
        ltc_move_to_ififo(base, iv, ivSize);
        if (iv_only && ivSize)
        {
            retval = ltc_wait(base);
            if (kStatus_Success != retval)
            {
                return retval;
            }
        }
        base->AADSZ = LTC_AADSZ_AL(aad_only) | ((aadSize)&LTC_DS_DS_MASK);
        ltc_move_to_ififo(base, aad, aadSize);
        if (aad_only && aadSize)
        {
            retval = ltc_wait(base);
            if (kStatus_Success != retval)
            {
                return retval;
            }
        }

        if (inputSize)
        {
            /* Workaround for the LTC Data Size register update errata TKT261180 */
            while (16U < base->DS)
            {
            }

            ltc_symmetric_process_data(base, &src[0], inputSize, &dst[0]);
        }
    }
    else
    {
        ltc_symmetric_init(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeGCM, encryptMode);
        modeReg = base->MD;

        /* process IV */
        if (ivSize)
        {
            /* last chunk of IV is always INITIALIZE (for GHASH to occur) */
            retval = ltc_aes_gcm_process_iv_aad(base, iv, ivSize, modeReg, true, LTC_AES_GCM_TYPE_IV, kLTC_ModeInit);
            if (kStatus_Success != retval)
            {
                return retval;
            }
        }

        /* process AAD */
        if (aadSize)
        {
            /* AS mode to process last chunk of AAD. it differs if we are in GMAC or GCM */
            ltc_mode_t lastModeReg;
            if (0 == inputSize)
            {
                /* if there is no DATA, set mode to compute final MAC. this is GMAC mode */
                lastModeReg = kLTC_ModeInitFinal;
            }
            else
            {
                /* there are confidential DATA. so process last chunk of AAD in UPDATE mode */
                lastModeReg = kLTC_ModeUpdate;
            }
            retval = ltc_aes_gcm_process_iv_aad(base, aad, aadSize, modeReg, true, LTC_AES_GCM_TYPE_AAD, lastModeReg);
            if (kStatus_Success != retval)
            {
                return retval;
            }
        }

        /* there are DATA. */
        if (inputSize)
        {
            /* set algorithm state to UPDATE */
            modeReg &= ~LTC_MD_AS_MASK;
            modeReg |= kLTC_ModeUpdate;
            base->MD = modeReg;
            retval =
                ltc_symmetric_process_data_multiple(base, &src[0], inputSize, &dst[0], modeReg, kLTC_ModeInitFinal);
        }
    }
    if (kStatus_Success != retval)
    {
        return retval;
    }
    retval = ltc_aes_process_tag(base, tag, tagSize, modeReg, LTC_GCM_TAG_IDX);
    return retval;
}

/*******************************************************************************
 * GCM Code public
 ******************************************************************************/
status_t LTC_AES_EncryptTagGcm(LTC_Type *base,
                               const uint8_t *plaintext,
                               uint8_t *ciphertext,
                               uint32_t size,
                               const uint8_t *iv,
                               uint32_t ivSize,
                               const uint8_t *aad,
                               uint32_t aadSize,
                               const uint8_t *key,
                               uint32_t keySize,
                               uint8_t *tag,
                               uint32_t tagSize)
{
    status_t status;

    status = ltc_aes_gcm_process(base, kLTC_ModeEncrypt, plaintext, size, iv, ivSize, aad, aadSize, key, keySize,
                                 ciphertext, tag, tagSize);

    ltc_clear_all(base, false);
    return status;
}

status_t LTC_AES_DecryptTagGcm(LTC_Type *base,
                               const uint8_t *ciphertext,
                               uint8_t *plaintext,
                               uint32_t size,
                               const uint8_t *iv,
                               uint32_t ivSize,
                               const uint8_t *aad,
                               uint32_t aadSize,
                               const uint8_t *key,
                               uint32_t keySize,
                               const uint8_t *tag,
                               uint32_t tagSize)
{
    uint8_t temp_tag[16] = {0}; /* max. octet length of Integrity Check Value ICV (tag) is 16 */
    uint8_t *tag_ptr;
    status_t status;

    tag_ptr = NULL;
    if (tag)
    {
        ltc_memcpy(temp_tag, tag, tagSize);
        tag_ptr = &temp_tag[0];
    }
    status = ltc_aes_gcm_process(base, kLTC_ModeDecrypt, ciphertext, size, iv, ivSize, aad, aadSize, key, keySize,
                                 plaintext, tag_ptr, tagSize);

    ltc_clear_all(base, false);
    return status;
}
#endif /* FSL_FEATURE_LTC_HAS_GCM */

/*******************************************************************************
 * CCM Code static
 ******************************************************************************/
static status_t ltc_aes_ccm_check_input_args(LTC_Type *base,
                                             const uint8_t *src,
                                             const uint8_t *iv,
                                             const uint8_t *key,
                                             uint8_t *dst,
                                             uint32_t ivSize,
                                             uint32_t aadSize,
                                             uint32_t keySize,
                                             uint32_t tagSize)
{
    if (!base)
    {
        return kStatus_InvalidArgument;
    }

    /* tag can be NULL to skip tag processing */
    if ((!src) || (!iv) || (!key) || (!dst))
    {
        return kStatus_InvalidArgument;
    }

    /* size of Nonce (ivSize) must be element of 7,8,9,10,11,12,13 */
    if ((ivSize < 7u) || (ivSize > 13u))
    {
        return kStatus_InvalidArgument;
    }
    /* octet length of MAC (tagSize) must be element of 4,6,8,10,12,14,16 for tag processing or zero to skip tag
     * processing */
    if (((tagSize > 0) && (tagSize < 4u)) || (tagSize > 16u) || (tagSize & 1u))
    {
        return kStatus_InvalidArgument;
    }

    /* check if keySize is supported */
    if (!ltc_check_key_size(keySize))
    {
        return kStatus_InvalidArgument;
    }

    /* LTC does not support more AAD than this */
    if (aadSize >= 65280u)
    {
        return kStatus_InvalidArgument;
    }
    return kStatus_Success;
}

static uint32_t swap_bytes(uint32_t in)
{
    return (((in & 0x000000ffu) << 24) | ((in & 0x0000ff00u) << 8) | ((in & 0x00ff0000u) >> 8) |
            ((in & 0xff000000u) >> 24));
}

static void ltc_aes_ccm_context_init(
    LTC_Type *base, uint32_t inputSize, const uint8_t *iv, uint32_t ivSize, uint32_t aadSize, uint32_t tagSize)
{
    ltc_xcm_block_t blk;
    ltc_xcm_block_t blkZero = {{0x0u, 0x0u, 0x0u, 0x0u}};

    int q; /* octet length of binary representation of the octet length of the payload. computed as (15 - n), where n is
              length of nonce(=ivSize) */
    uint8_t flags; /* flags field in B0 and CTR0 */

    /* compute B0 */
    ltc_memcpy(&blk, &blkZero, sizeof(blk));
    /* tagSize - size of output MAC */
    q = 15 - ivSize;
    flags = (uint8_t)(8 * ((tagSize - 2) / 2) + q - 1); /* 8*M' + L' */
    if (aadSize)
    {
        flags |= 0x40; /* Adata */
    }
    blk.b[0] = flags;                  /* flags field */
    blk.w[3] = swap_bytes(inputSize);  /* message size, most significant byte first */
    ltc_memcpy(&blk.b[1], iv, ivSize); /* nonce field */

    /* Write B0 data to the context register.
     */
    ltc_set_context(base, &blk.b[0], 16, 0);

    /* Write CTR0 to the context register.
     */
    ltc_memcpy(&blk, &blkZero, sizeof(blk)); /* ctr(0) field = zero */
    blk.b[0] = q - 1;                        /* flags field */
    ltc_memcpy(&blk.b[1], iv, ivSize);       /* nonce field */
    ltc_set_context(base, &blk.b[0], 16, 4);
}

static status_t ltc_aes_ccm_process_aad(
    LTC_Type *base, uint32_t inputSize, const uint8_t *aad, uint32_t aadSize, ltc_mode_t *modeReg)
{
    ltc_xcm_block_t blk = {{0x0u, 0x0u, 0x0u, 0x0u}};
    uint32_t swapped; /* holds byte swap of uint32_t */
    status_t retval;

    if (aadSize)
    {
        bool aad_only;
        bool aad_single_session;

        uint32_t sz = 0;

        aad_only = inputSize == 0u;
        aad_single_session = (((aadSize + 2u) + 15u) & 0xfffffff0u) <= LTC_FIFO_SZ_MAX_DOWN_ALGN;

        /* limit by CCM spec: 2^16 - 2^8 = 65280 */

        /* encoding is two octets, msbyte first */
        swapped = swap_bytes(aadSize);
        ltc_memcpy(&blk.b[0], ((uint8_t *)&swapped) + sizeof(uint16_t), sizeof(uint16_t));

        sz = aadSize > 14u ? 14u : aadSize; /* limit aad to the end of 16 bytes blk */
        ltc_memcpy(&blk.b[2], aad, sz);     /* fill B1 with aad */

        if (aad_single_session)
        {
            base->AADSZ = LTC_AADSZ_AL(aad_only) | ((aadSize + 2U) & LTC_DS_DS_MASK);
            /* move first AAD block (16 bytes block B1) to FIFO */
            ltc_move_block_to_ififo(base, &blk, sizeof(blk));
        }
        else
        {
            base->AADSZ = LTC_AADSZ_AL(true) | (16U);
            /* move first AAD block (16 bytes block B1) to FIFO */
            ltc_move_block_to_ififo(base, &blk, sizeof(blk));
        }

        /* track consumed AAD. sz bytes have been moved to fifo. */
        aadSize -= sz;
        aad += sz;

        if (aad_single_session)
        {
            /* move remaining AAD to FIFO, then return, to continue with MDATA */
            ltc_move_to_ififo(base, aad, aadSize);
        }
        else if (aadSize == 0u)
        {
            retval = ltc_wait(base);
            if (kStatus_Success != retval)
            {
                return retval;
            }
        }
        else
        {
            while (aadSize)
            {
                retval = ltc_wait(base);
                if (kStatus_Success != retval)
                {
                    return retval;
                }

                *modeReg &= ~LTC_MD_AS_MASK;
                *modeReg |= (uint32_t)kLTC_ModeUpdate;
                base->MD = *modeReg;

                sz = LTC_FIFO_SZ_MAX_DOWN_ALGN;
                if (aadSize < sz)
                {
                    base->AADSZ = LTC_AADSZ_AL(aad_only) | (aadSize & LTC_DS_DS_MASK);
                    ltc_move_to_ififo(base, aad, aadSize);
                    aadSize = 0;
                }
                else
                {
                    base->AADSZ = LTC_AADSZ_AL(true) | (sz & LTC_DS_DS_MASK);
                    ltc_move_to_ififo(base, aad, sz);
                    aadSize -= sz;
                    aad += sz;
                }
            } /* end while */
        }     /* end else */
    }         /* end if */
    return kStatus_Success;
}

static status_t ltc_aes_ccm_process(LTC_Type *base,
                                    ltc_mode_encrypt_t encryptMode,
                                    const uint8_t *src,
                                    uint32_t inputSize,
                                    const uint8_t *iv,
                                    uint32_t ivSize,
                                    const uint8_t *aad,
                                    uint32_t aadSize,
                                    const uint8_t *key,
                                    uint32_t keySize,
                                    uint8_t *dst,
                                    uint8_t *tag,
                                    uint32_t tagSize)
{
    status_t retval;          /* return value */
    uint32_t max_ltc_fifo_sz; /* maximum data size that we can put to LTC FIFO in one session. 12-bit limit. */
    ltc_mode_t modeReg;       /* read and write LTC mode register */

    bool single_ses_proc_all; /* aad and src data can be processed in one session */

    retval = ltc_aes_ccm_check_input_args(base, src, iv, key, dst, ivSize, aadSize, keySize, tagSize);

    /* API input validation */
    if (kStatus_Success != retval)
    {
        return retval;
    }

    max_ltc_fifo_sz = LTC_DS_DS_MASK; /* 12-bit field limit */

    /* Write value to LTC AADSIZE will be (aadSize+2) value.
     * The value will be rounded up to next 16 byte boundary and added to Data Size register.
     * We then add inputSize to Data Size register. If the resulting Data Size is less than max_ltc_fifo_sz
     * then all can be processed in one session INITIALIZE/FINALIZE.
     * Otherwise, we have to split into multiple session, going through INITIALIZE, UPDATE (if required) and FINALIZE.
     */
    single_ses_proc_all = ((((aadSize + 2) + 15u) & 0xfffffff0u) + inputSize) <= max_ltc_fifo_sz;

    /* setup key, algorithm and set the alg.state to INITIALIZE */
    if (single_ses_proc_all)
    {
        ltc_symmetric_init_final(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeCCM, encryptMode);
    }
    else
    {
        ltc_symmetric_init(base, key, keySize, kLTC_AlgorithmAES, kLTC_ModeCCM, encryptMode);
    }
    modeReg = base->MD;

    /* Initialize LTC context for AES CCM: block B0 and initial counter CTR0 */
    ltc_aes_ccm_context_init(base, inputSize, iv, ivSize, aadSize, tagSize);

    /* Process additional authentication data, if there are any.
     * Need to split the job into individual sessions of up to 4096 bytes, due to LTC IFIFO data size limit.
     */
    retval = ltc_aes_ccm_process_aad(base, inputSize, aad, aadSize, &modeReg);
    if (kStatus_Success != retval)
    {
        return retval;
    }

    /* Workaround for the LTC Data Size register update errata TKT261180 */
    if (inputSize)
    {
        while (16u < base->DS)
        {
        }
    }

    /* Process message */
    if (single_ses_proc_all)
    {
        retval = ltc_symmetric_process_data(base, &src[0], inputSize, &dst[0]);
    }
    else
    {
        retval = ltc_symmetric_process_data_multiple(base, &src[0], inputSize, &dst[0], modeReg, kLTC_ModeFinalize);
    }
    if (kStatus_Success != retval)
    {
        return retval;
    }
    retval = ltc_aes_process_tag(base, tag, tagSize, modeReg, LTC_CCM_TAG_IDX);
    return retval;
}

/*******************************************************************************
 * CCM Code public
 ******************************************************************************/
status_t LTC_AES_EncryptTagCcm(LTC_Type *base,
                               const uint8_t *plaintext,
                               uint8_t *ciphertext,
                               uint32_t size,
                               const uint8_t *iv,
                               uint32_t ivSize,
                               const uint8_t *aad,
                               uint32_t aadSize,
                               const uint8_t *key,
                               uint32_t keySize,
                               uint8_t *tag,
                               uint32_t tagSize)
{
    status_t status;
    status = ltc_aes_ccm_process(base, kLTC_ModeEncrypt, plaintext, size, iv, ivSize, aad, aadSize, key, keySize,
                                 ciphertext, tag, tagSize);

    ltc_clear_all(base, false);
    return status;
}

status_t LTC_AES_DecryptTagCcm(LTC_Type *base,
                               const uint8_t *ciphertext,
                               uint8_t *plaintext,
                               uint32_t size,
                               const uint8_t *iv,
                               uint32_t ivSize,
                               const uint8_t *aad,
                               uint32_t aadSize,
                               const uint8_t *key,
                               uint32_t keySize,
                               const uint8_t *tag,
                               uint32_t tagSize)
{
    uint8_t temp_tag[16] = {0}; /* max. octet length of MAC (tag) is 16 */
    uint8_t *tag_ptr;
    status_t status;

    tag_ptr = NULL;
    if (tag)
    {
        ltc_memcpy(temp_tag, tag, tagSize);
        tag_ptr = &temp_tag[0];
    }

    status = ltc_aes_ccm_process(base, kLTC_ModeDecrypt, ciphertext, size, iv, ivSize, aad, aadSize, key, keySize,
                                 plaintext, tag_ptr, tagSize);

    ltc_clear_all(base, false);
    return status;
}

#if defined(FSL_FEATURE_LTC_HAS_DES) && FSL_FEATURE_LTC_HAS_DES
/*******************************************************************************
 * DES / 3DES Code static
 ******************************************************************************/
static status_t ltc_des_process(LTC_Type *base,
                                const uint8_t *input,
                                uint8_t *output,
                                uint32_t size,
                                const uint8_t iv[LTC_DES_IV_SIZE],
                                const uint8_t key[LTC_DES_KEY_SIZE],
                                ltc_mode_symmetric_alg_t modeAs,
                                ltc_mode_encrypt_t modeEnc)
{
    status_t retval;

    /* all but OFB, size must be 8-byte multiple */
    if ((modeAs != kLTC_ModeOFB) && ((size < 8u) || (size % 8u)))
    {
        return kStatus_InvalidArgument;
    }

    /* Initialize algorithm state. */
    ltc_symmetric_update(base, &key[0], LTC_DES_KEY_SIZE, kLTC_AlgorithmDES, modeAs, modeEnc);

    if ((modeAs != kLTC_ModeECB))
    {
        ltc_set_context(base, iv, LTC_DES_IV_SIZE, 0);
    }

    /* Process data and return status. */
    retval = ltc_process_message_in_sessions(base, input, size, output);
    ltc_clear_all(base, false);
    return retval;
}

status_t ltc_3des_check_input_args(ltc_mode_symmetric_alg_t modeAs,
                                   uint32_t size,
                                   const uint8_t *key1,
                                   const uint8_t *key2)
{
    /* all but OFB, size must be 8-byte multiple */
    if ((modeAs != kLTC_ModeOFB) && ((size < 8u) || (size % 8u)))
    {
        return kStatus_InvalidArgument;
    }

    if ((key1 == NULL) || (key2 == NULL))
    {
        return kStatus_InvalidArgument;
    }
    return kStatus_Success;
}

static status_t ltc_3des_process(LTC_Type *base,
                                 const uint8_t *input,
                                 uint8_t *output,
                                 uint32_t size,
                                 const uint8_t iv[LTC_DES_IV_SIZE],
                                 const uint8_t key1[LTC_DES_KEY_SIZE],
                                 const uint8_t key2[LTC_DES_KEY_SIZE],
                                 const uint8_t key3[LTC_DES_KEY_SIZE],
                                 ltc_mode_symmetric_alg_t modeAs,
                                 ltc_mode_encrypt_t modeEnc)
{
    status_t retval;
    uint8_t key[LTC_DES_KEY_SIZE * 3];
    uint8_t keySize = LTC_DES_KEY_SIZE * 2;

    retval = ltc_3des_check_input_args(modeAs, size, key1, key2);
    if (kStatus_Success != retval)
    {
        return retval;
    }

    ltc_memcpy(&key[0], &key1[0], LTC_DES_KEY_SIZE);
    ltc_memcpy(&key[LTC_DES_KEY_SIZE], &key2[0], LTC_DES_KEY_SIZE);
    if (key3)
    {
        ltc_memcpy(&key[LTC_DES_KEY_SIZE * 2], &key3[0], LTC_DES_KEY_SIZE);
        keySize = sizeof(key);
    }

    /* Initialize algorithm state. */
    ltc_symmetric_update(base, &key[0], keySize, kLTC_Algorithm3DES, modeAs, modeEnc);

    if ((modeAs != kLTC_ModeECB))
    {
        ltc_set_context(base, iv, LTC_DES_IV_SIZE, 0);
    }

    /* Process data and return status. */
    retval = ltc_process_message_in_sessions(base, input, size, output);
    ltc_clear_all(base, false);
    return retval;
}
/*******************************************************************************
 * DES / 3DES Code public
 ******************************************************************************/
status_t LTC_DES_EncryptEcb(
    LTC_Type *base, const uint8_t *plaintext, uint8_t *ciphertext, uint32_t size, const uint8_t key[LTC_DES_KEY_SIZE])
{
    return ltc_des_process(base, plaintext, ciphertext, size, NULL, key, kLTC_ModeECB, kLTC_ModeEncrypt);
}

status_t LTC_DES_DecryptEcb(
    LTC_Type *base, const uint8_t *ciphertext, uint8_t *plaintext, uint32_t size, const uint8_t key[LTC_DES_KEY_SIZE])
{
    return ltc_des_process(base, ciphertext, plaintext, size, NULL, key, kLTC_ModeECB, kLTC_ModeDecrypt);
}

status_t LTC_DES_EncryptCbc(LTC_Type *base,
                            const uint8_t *plaintext,
                            uint8_t *ciphertext,
                            uint32_t size,
                            const uint8_t iv[LTC_DES_IV_SIZE],
                            const uint8_t key[LTC_DES_KEY_SIZE])
{
    return ltc_des_process(base, plaintext, ciphertext, size, iv, key, kLTC_ModeCBC, kLTC_ModeEncrypt);
}

status_t LTC_DES_DecryptCbc(LTC_Type *base,
                            const uint8_t *ciphertext,
                            uint8_t *plaintext,
                            uint32_t size,
                            const uint8_t iv[LTC_DES_IV_SIZE],
                            const uint8_t key[LTC_DES_KEY_SIZE])
{
    return ltc_des_process(base, ciphertext, plaintext, size, iv, key, kLTC_ModeCBC, kLTC_ModeDecrypt);
}

status_t LTC_DES_EncryptCfb(LTC_Type *base,
                            const uint8_t *plaintext,
                            uint8_t *ciphertext,
                            uint32_t size,
                            const uint8_t iv[LTC_DES_IV_SIZE],
                            const uint8_t key[LTC_DES_KEY_SIZE])
{
    return ltc_des_process(base, plaintext, ciphertext, size, iv, key, kLTC_ModeCFB, kLTC_ModeEncrypt);
}

status_t LTC_DES_DecryptCfb(LTC_Type *base,
                            const uint8_t *ciphertext,
                            uint8_t *plaintext,
                            uint32_t size,
                            const uint8_t iv[LTC_DES_IV_SIZE],
                            const uint8_t key[LTC_DES_KEY_SIZE])
{
    return ltc_des_process(base, ciphertext, plaintext, size, iv, key, kLTC_ModeCFB, kLTC_ModeDecrypt);
}

status_t LTC_DES_EncryptOfb(LTC_Type *base,
                            const uint8_t *plaintext,
                            uint8_t *ciphertext,
                            uint32_t size,
                            const uint8_t iv[LTC_DES_IV_SIZE],
                            const uint8_t key[LTC_DES_KEY_SIZE])
{
    return ltc_des_process(base, plaintext, ciphertext, size, iv, key, kLTC_ModeOFB, kLTC_ModeEncrypt);
}

status_t LTC_DES_DecryptOfb(LTC_Type *base,
                            const uint8_t *ciphertext,
                            uint8_t *plaintext,
                            uint32_t size,
                            const uint8_t iv[LTC_DES_IV_SIZE],
                            const uint8_t key[LTC_DES_KEY_SIZE])
{
    return ltc_des_process(base, ciphertext, plaintext, size, iv, key, kLTC_ModeOFB, kLTC_ModeDecrypt);
}

status_t LTC_DES2_EncryptEcb(LTC_Type *base,
                             const uint8_t *plaintext,
                             uint8_t *ciphertext,
                             uint32_t size,
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, plaintext, ciphertext, size, NULL, key1, key2, NULL, kLTC_ModeECB, kLTC_ModeEncrypt);
}

status_t LTC_DES3_EncryptEcb(LTC_Type *base,
                             const uint8_t *plaintext,
                             uint8_t *ciphertext,
                             uint32_t size,
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE],
                             const uint8_t key3[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, plaintext, ciphertext, size, NULL, key1, key2, key3, kLTC_ModeECB, kLTC_ModeEncrypt);
}

status_t LTC_DES2_DecryptEcb(LTC_Type *base,
                             const uint8_t *ciphertext,
                             uint8_t *plaintext,
                             uint32_t size,
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, ciphertext, plaintext, size, NULL, key1, key2, NULL, kLTC_ModeECB, kLTC_ModeDecrypt);
}

status_t LTC_DES3_DecryptEcb(LTC_Type *base,
                             const uint8_t *ciphertext,
                             uint8_t *plaintext,
                             uint32_t size,
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE],
                             const uint8_t key3[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, ciphertext, plaintext, size, NULL, key1, key2, key3, kLTC_ModeECB, kLTC_ModeDecrypt);
}

status_t LTC_DES2_EncryptCbc(LTC_Type *base,
                             const uint8_t *plaintext,
                             uint8_t *ciphertext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, plaintext, ciphertext, size, iv, key1, key2, NULL, kLTC_ModeCBC, kLTC_ModeEncrypt);
}

status_t LTC_DES3_EncryptCbc(LTC_Type *base,
                             const uint8_t *plaintext,
                             uint8_t *ciphertext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE],
                             const uint8_t key3[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, plaintext, ciphertext, size, iv, key1, key2, key3, kLTC_ModeCBC, kLTC_ModeEncrypt);
}

status_t LTC_DES2_DecryptCbc(LTC_Type *base,
                             const uint8_t *ciphertext,
                             uint8_t *plaintext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, ciphertext, plaintext, size, iv, key1, key2, NULL, kLTC_ModeCBC, kLTC_ModeDecrypt);
}

status_t LTC_DES3_DecryptCbc(LTC_Type *base,
                             const uint8_t *ciphertext,
                             uint8_t *plaintext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE],
                             const uint8_t key3[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, ciphertext, plaintext, size, iv, key1, key2, key3, kLTC_ModeCBC, kLTC_ModeDecrypt);
}

status_t LTC_DES2_EncryptCfb(LTC_Type *base,
                             const uint8_t *plaintext,
                             uint8_t *ciphertext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, plaintext, ciphertext, size, iv, key1, key2, NULL, kLTC_ModeCFB, kLTC_ModeEncrypt);
}

status_t LTC_DES3_EncryptCfb(LTC_Type *base,
                             const uint8_t *plaintext,
                             uint8_t *ciphertext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE],
                             const uint8_t key3[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, plaintext, ciphertext, size, iv, key1, key2, key3, kLTC_ModeCFB, kLTC_ModeEncrypt);
}

status_t LTC_DES2_DecryptCfb(LTC_Type *base,
                             const uint8_t *ciphertext,
                             uint8_t *plaintext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, ciphertext, plaintext, size, iv, key1, key2, NULL, kLTC_ModeCFB, kLTC_ModeDecrypt);
}

status_t LTC_DES3_DecryptCfb(LTC_Type *base,
                             const uint8_t *ciphertext,
                             uint8_t *plaintext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE],
                             const uint8_t key3[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, ciphertext, plaintext, size, iv, key1, key2, key3, kLTC_ModeCFB, kLTC_ModeDecrypt);
}

status_t LTC_DES2_EncryptOfb(LTC_Type *base,
                             const uint8_t *plaintext,
                             uint8_t *ciphertext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, plaintext, ciphertext, size, iv, key1, key2, NULL, kLTC_ModeOFB, kLTC_ModeEncrypt);
}

status_t LTC_DES3_EncryptOfb(LTC_Type *base,
                             const uint8_t *plaintext,
                             uint8_t *ciphertext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE],
                             const uint8_t key3[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, plaintext, ciphertext, size, iv, key1, key2, key3, kLTC_ModeOFB, kLTC_ModeEncrypt);
}

status_t LTC_DES2_DecryptOfb(LTC_Type *base,
                             const uint8_t *ciphertext,
                             uint8_t *plaintext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, ciphertext, plaintext, size, iv, key1, key2, NULL, kLTC_ModeOFB, kLTC_ModeDecrypt);
}

status_t LTC_DES3_DecryptOfb(LTC_Type *base,
                             const uint8_t *ciphertext,
                             uint8_t *plaintext,
                             uint32_t size,
                             const uint8_t iv[LTC_DES_IV_SIZE],
                             const uint8_t key1[LTC_DES_KEY_SIZE],
                             const uint8_t key2[LTC_DES_KEY_SIZE],
                             const uint8_t key3[LTC_DES_KEY_SIZE])
{
    return ltc_3des_process(base, ciphertext, plaintext, size, iv, key1, key2, key3, kLTC_ModeOFB, kLTC_ModeDecrypt);
}
#endif /* FSL_FEATURE_LTC_HAS_DES */

/*******************************************************************************
 * HASH Definitions
 ******************************************************************************/
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
#define LTC_SHA_BLOCK_SIZE 64                  /*!< SHA-1, SHA-224 & SHA-256 block size  */
#define LTC_HASH_BLOCK_SIZE LTC_SHA_BLOCK_SIZE /*!< LTC hash block size  */

enum _ltc_sha_digest_len
{
    kLTC_RunLenSha1 = 28u,
    kLTC_OutLenSha1 = 20u,
    kLTC_RunLenSha224 = 40u,
    kLTC_OutLenSha224 = 28u,
    kLTC_RunLenSha256 = 40u,
    kLTC_OutLenSha256 = 32u,
};
#else
#define LTC_HASH_BLOCK_SIZE LTC_AES_BLOCK_SIZE /*!< LTC hash block size  */
#endif                                         /* FSL_FEATURE_LTC_HAS_SHA */

/*! Internal states of the HASH creation process */
typedef enum _ltc_hash_algo_state
{
    kLTC_HashInit = 1u, /*!< Key in the HASH context is the input key. */
    kLTC_HashUpdate,    /*!< HASH context has algorithm specific context: MAC, K2 and K3 (XCBC-MAC), MAC and L (CMAC),
                           running digest (MDHA). Key in the HASH context is the derived key. */
} ltc_hash_algo_state_t;

/*! 16/64-byte block represented as byte array or 4/16 32-bit words */
typedef union _ltc_hash_block
{
    uint32_t w[LTC_HASH_BLOCK_SIZE / 4]; /*!< array of 32-bit words */
    uint8_t b[LTC_HASH_BLOCK_SIZE];      /*!< byte array */
} ltc_hash_block_t;

/*! Definitions of indexes into hash context array */
typedef enum _ltc_hash_ctx_indexes
{
    kLTC_HashCtxKeyStartIdx = 12, /*!< context word array index where key is stored */
    kLTC_HashCtxKeySize = 20,     /*!< context word array index where key size is stored */
    kLTC_HashCtxNumWords = 21,    /*!< number of context array 32-bit words  */
} ltc_hash_ctx_indexes;

typedef struct _ltc_hash_ctx_internal
{
    ltc_hash_block_t blk; /*!< memory buffer. only full 64/16-byte blocks are written to LTC during hash updates */
    uint32_t blksz;       /*!< number of valid bytes in memory buffer */
    LTC_Type *base;       /*!< LTC peripheral base address */
    ltc_hash_algo_t algo; /*!< selected algorithm from the set of supported algorithms in ltc_drv_hash_algo */
    ltc_hash_algo_state_t state;         /*!< finite machine state of the hash software process */
    uint32_t word[kLTC_HashCtxNumWords]; /*!< LTC module context that needs to be saved/restored between LTC jobs */
} ltc_hash_ctx_internal_t;

/*******************************************************************************
 * HASH Code static
 ******************************************************************************/
static status_t ltc_hash_check_input_alg(ltc_hash_algo_t algo)
{
    if ((algo != kLTC_XcbcMac) && (algo != kLTC_Cmac)
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
        && (algo != kLTC_Sha1) && (algo != kLTC_Sha224) && (algo != kLTC_Sha256)
#endif /* FSL_FEATURE_LTC_HAS_SHA */
            )
    {
        return kStatus_InvalidArgument;
    }
    return kStatus_Success;
}

static inline bool ltc_hash_alg_is_cmac(ltc_hash_algo_t algo)
{
    return ((algo == kLTC_XcbcMac) || (algo == kLTC_Cmac));
}

#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
static inline bool ltc_hash_alg_is_sha(ltc_hash_algo_t algo)
{
    return ((algo == kLTC_Sha1) || (algo == kLTC_Sha224) || (algo == kLTC_Sha256));
}
#endif /* FSL_FEATURE_LTC_HAS_SHA */

static status_t ltc_hash_check_input_args(
    LTC_Type *base, ltc_hash_ctx_t *ctx, ltc_hash_algo_t algo, const uint8_t *key, uint32_t keySize)
{
    /* Check validity of input algorithm */
    if (kStatus_Success != ltc_hash_check_input_alg(algo))
    {
        return kStatus_InvalidArgument;
    }

    if ((NULL == ctx) || (NULL == base))
    {
        return kStatus_InvalidArgument;
    }

    if (ltc_hash_alg_is_cmac(algo))
    {
        if ((NULL == key) || (!ltc_check_key_size(keySize)))
        {
            return kStatus_InvalidArgument;
        }
    }

    return kStatus_Success;
}

static status_t ltc_hash_check_context(ltc_hash_ctx_internal_t *ctxInternal, const uint8_t *data)
{
    if ((NULL == data) || (NULL == ctxInternal) || (NULL == ctxInternal->base) ||
        (kStatus_Success != ltc_hash_check_input_alg(ctxInternal->algo)))
    {
        return kStatus_InvalidArgument;
    }
    return kStatus_Success;
}

static uint32_t ltc_hash_algo2mode(ltc_hash_algo_t algo, ltc_mode_algorithm_state_t asMode, uint32_t *algOutSize)
{
    uint32_t modeReg = 0u;
    uint32_t outSize = 0u;

    /* Set LTC algorithm */
    switch (algo)
    {
        case kLTC_XcbcMac:
            modeReg = (uint32_t)kLTC_AlgorithmAES | (uint32_t)kLTC_ModeXCBCMAC;
            outSize = 16u;
            break;
        case kLTC_Cmac:
            modeReg = (uint32_t)kLTC_AlgorithmAES | (uint32_t)kLTC_ModeCMAC;
            outSize = 16u;
            break;
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
        case kLTC_Sha1:
            modeReg = (uint32_t)kLTC_AlgorithmSHA1;
            outSize = kLTC_OutLenSha1;
            break;
        case kLTC_Sha224:
            modeReg = (uint32_t)kLTC_AlgorithmSHA224;
            outSize = kLTC_OutLenSha224;
            break;
        case kLTC_Sha256:
            modeReg = (uint32_t)kLTC_AlgorithmSHA256;
            outSize = kLTC_OutLenSha256;
            break;
#endif /* FSL_FEATURE_LTC_HAS_SHA */
        default:
            break;
    }

    modeReg |= (uint32_t)asMode;
    if (algOutSize)
    {
        *algOutSize = outSize;
    }

    return modeReg;
}

static void ltc_hash_engine_init(ltc_hash_ctx_internal_t *ctx)
{
    uint8_t *key;
    uint32_t keySize;
    LTC_Type *base;
    ltc_mode_symmetric_alg_t algo;

    base = ctx->base;
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
    if (ltc_hash_alg_is_cmac(ctx->algo))
    {
#endif  /* FSL_FEATURE_LTC_HAS_SHA */
        /*
         *  word[kLtcCmacCtxKeySize] = key_length
         *  word[1-8] = key
         */
        keySize = ctx->word[kLTC_HashCtxKeySize];
        key = (uint8_t *)&ctx->word[kLTC_HashCtxKeyStartIdx];

        /* set LTC mode register to INITIALIZE */
        algo = (ctx->algo == kLTC_XcbcMac) ? kLTC_ModeXCBCMAC : kLTC_ModeCMAC;
        ltc_symmetric_init(base, key, keySize, kLTC_AlgorithmAES, algo, kLTC_ModeEncrypt);
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
    }
    else if (ltc_hash_alg_is_sha(ctx->algo))
    {
        /* Clear internal register states. */
        base->CW = (uint32_t)kLTC_ClearAll;

        /* Set byte swap on for several registers we will be reading and writing
         * user data to/from. */
        base->CTL |= kLTC_CtrlSwapAll;
    }
    else
    {
        /* do nothing in this case */
    }
#endif /* FSL_FEATURE_LTC_HAS_SHA */
}

static void ltc_hash_save_context(ltc_hash_ctx_internal_t *ctx)
{
    uint32_t sz;
    LTC_Type *base;

    base = ctx->base;
    /* Get context size */
    switch (ctx->algo)
    {
        case kLTC_XcbcMac:
            /*
            *  word[0-3] = mac
            *  word[3-7] = k3
            *  word[8-11] = k2
            *  word[kLtcCmacCtxKeySize] = keySize
            */
            sz = 12 * sizeof(uint32_t);
            break;
        case kLTC_Cmac:
            /*
            *  word[0-3] = mac
            *  word[3-7] = L */
            sz = 8 * sizeof(uint32_t);
            break;
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
        case kLTC_Sha1:
            sz = (kLTC_RunLenSha1);
            break;
        case kLTC_Sha224:
            sz = (kLTC_RunLenSha224);
            break;
        case kLTC_Sha256:
            sz = (kLTC_RunLenSha256);
            break;
#endif /* FSL_FEATURE_LTC_HAS_SHA */
        default:
            sz = 0;
            break;
    }

    ltc_get_context(base, (uint8_t *)&ctx->word[0], sz, 0);

    if (true == ltc_hash_alg_is_cmac(ctx->algo))
    {
        /* word[12-19] = key */
        ltc_get_key(base, (uint8_t *)&ctx->word[kLTC_HashCtxKeyStartIdx], ctx->word[kLTC_HashCtxKeySize]);
    }
}

static void ltc_hash_restore_context(ltc_hash_ctx_internal_t *ctx)
{
    uint32_t sz;
    uint32_t keySize;
    LTC_Type *base;

    base = ctx->base;
    /* Get context size */
    switch (ctx->algo)
    {
        case kLTC_XcbcMac:
            /*
            *  word[0-3] = mac
            *  word[3-7] = k3
            *  word[8-11] = k2
            *  word[kLtcCmacCtxKeySize] = keySize
            */
            sz = 12 * sizeof(uint32_t);
            break;
        case kLTC_Cmac:
            /*
            *  word[0-3] = mac
            *  word[3-7] = L */
            sz = 8 * sizeof(uint32_t);
            break;
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
        case kLTC_Sha1:
            sz = (kLTC_RunLenSha1);
            break;
        case kLTC_Sha224:
            sz = (kLTC_RunLenSha224);
            break;
        case kLTC_Sha256:
            sz = (kLTC_RunLenSha256);
            break;
#endif /* FSL_FEATURE_LTC_HAS_SHA */
        default:
            sz = 0;
            break;
    }

    ltc_set_context(base, (const uint8_t *)&ctx->word[0], sz, 0);

    if (ltc_hash_alg_is_cmac(ctx->algo))
    {
        /*
         *  word[12-19] = key
         *  word[kLtcCmacCtxKeySize] = keySize
         */
        base->CW = kLTC_ClearKey; /* clear Key and Key Size registers */

        keySize = ctx->word[kLTC_HashCtxKeySize];
        /* Write the key in place. */
        ltc_set_key(base, (const uint8_t *)&ctx->word[kLTC_HashCtxKeyStartIdx], keySize);

        /* Write the key size.  This must be done after writing the key, and this
         * action locks the ability to modify the key registers. */
        base->KS = keySize;
    }
}

static void ltc_hash_prepare_context_switch(LTC_Type *base)
{
    base->CW = (uint32_t)kLTC_ClearDataSize | (uint32_t)kLTC_ClearMode;
    base->STA = kLTC_StatusDoneIsr;
}

static uint32_t ltc_hash_get_block_size(ltc_hash_algo_t algo)
{
    if ((algo == kLTC_XcbcMac) || (algo == kLTC_Cmac))
    {
        return (uint32_t)LTC_AES_BLOCK_SIZE;
    }
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
    else if ((algo == kLTC_Sha1) || (algo == kLTC_Sha224) || (algo == kLTC_Sha256))
    {
        return (uint32_t)LTC_SHA_BLOCK_SIZE;
    }
    else
    {
        return 0;
    }
#else
    return 0;
#endif
}

static void ltc_hash_block_to_ififo(LTC_Type *base, const ltc_hash_block_t *blk, uint32_t numBytes, uint32_t blockSize)
{
    uint32_t i = 0;
    uint32_t words;

    words = numBytes / 4u;
    if (numBytes % 4u)
    {
        words++;
    }

    if (words > blockSize / 4u)
    {
        words = blockSize / 4u;
    }

    while (i < words)
    {
        if (0U == (base->FIFOSTA & LTC_FIFOSTA_IFF_MASK))
        {
            /* Copy data to the input FIFO. */
            base->IFIFO = blk->w[i++];
        }
    }
}

static void ltc_hash_move_to_ififo(ltc_hash_ctx_internal_t *ctx,
                                   const uint8_t *data,
                                   uint32_t dataSize,
                                   uint32_t blockSize)
{
    ltc_hash_block_t blkZero;
    uint32_t i;

    for (i = 0; i < ARRAY_SIZE(blkZero.w); i++)
    {
        blkZero.w[i] = 0;
    }

    while (dataSize)
    {
        if (dataSize >= blockSize)
        {
            ltc_memcpy(&ctx->blk, data, blockSize);
            ltc_hash_block_to_ififo(ctx->base, &ctx->blk, blockSize, blockSize);
            dataSize -= blockSize;
            data += blockSize;
        }
        else
        {
            /* last incomplete 16/64-bytes block of this message chunk */
            ltc_memcpy(&ctx->blk, &blkZero, sizeof(ctx->blk));
            ltc_memcpy(&ctx->blk, data, dataSize);
            ctx->blksz = dataSize;
            dataSize = 0;
        }
    }
}

static status_t ltc_hash_merge_and_flush_buf(ltc_hash_ctx_internal_t *ctx,
                                             const uint8_t *input,
                                             uint32_t inputSize,
                                             ltc_mode_t modeReg,
                                             uint32_t blockSize,
                                             uint32_t *consumedSize)
{
    uint32_t sz;
    LTC_Type *base;
    status_t status = kStatus_Success;

    base = ctx->base;
    sz = 0;
    if (ctx->blksz)
    {
        sz = blockSize - ctx->blksz;
        if (sz > inputSize)
        {
            sz = inputSize;
        }
        ltc_memcpy(ctx->blk.b + ctx->blksz, input, sz);
        input += sz;
        inputSize -= sz;
        ctx->blksz += sz;

        if (ctx->blksz == blockSize)
        {
            base->DS = blockSize;
            ltc_hash_block_to_ififo(base, &ctx->blk, blockSize, blockSize);
            ctx->blksz = 0;

            status = ltc_wait(base);
            if (kStatus_Success != status)
            {
                return status;
            }

            /* if there is still inputSize left, make sure LTC alg.state is set to UPDATE and continue */
            if (inputSize)
            {
                /* set algorithm state to UPDATE */
                modeReg &= ~LTC_MD_AS_MASK;
                modeReg |= kLTC_ModeUpdate;
                base->MD = modeReg;
            }
        }
    }
    if (consumedSize)
    {
        *consumedSize = sz;
    }
    return status;
}

static status_t ltc_hash_move_rest_to_context(
    ltc_hash_ctx_internal_t *ctx, const uint8_t *data, uint32_t dataSize, ltc_mode_t modeReg, uint32_t blockSize)
{
    status_t status = kStatus_Success;
    ltc_hash_block_t blkZero;
    uint32_t i;

    /* make blkZero clear */
    for (i = 0; i < ARRAY_SIZE(blkZero.w); i++)
    {
        blkZero.w[i] = 0;
    }

    while (dataSize)
    {
        if (dataSize > blockSize)
        {
            dataSize -= blockSize;
            data += blockSize;
        }
        else
        {
            if (dataSize + ctx->blksz > blockSize)
            {
                uint32_t sz;
                status = ltc_hash_merge_and_flush_buf(ctx, data, dataSize, modeReg, blockSize, &sz);
                if (kStatus_Success != status)
                {
                    return status;
                }
                data += sz;
                dataSize -= sz;
            }
            /* last incomplete 16/64-bytes block of this message chunk */
            ltc_memcpy(&ctx->blk, &blkZero, blockSize);
            ltc_memcpy(&ctx->blk, data, dataSize);
            ctx->blksz = dataSize;
            dataSize = 0;
        }
    }
    return status;
}

static status_t ltc_hash_process_input_data(ltc_hash_ctx_internal_t *ctx,
                                            const uint8_t *input,
                                            uint32_t inputSize,
                                            ltc_mode_t modeReg)
{
    uint32_t sz = 0;
    LTC_Type *base;
    uint32_t blockSize = 0;
    status_t status = kStatus_Success;

    blockSize = ltc_hash_get_block_size(ctx->algo);
    base = ctx->base;

    /* fill context struct blk and flush to LTC ififo in case it is full block */
    status = ltc_hash_merge_and_flush_buf(ctx, input, inputSize, modeReg, blockSize, &sz);
    if (kStatus_Success != status)
    {
        return status;
    }
    input += sz;
    inputSize -= sz;

    /* if there is still more than or equal to 64 bytes, move each 64 bytes through LTC */
    sz = LTC_DS_DS_MASK + 1u - LTC_HASH_BLOCK_SIZE;
    while (inputSize)
    {
        if (inputSize < sz)
        {
            uint32_t lastSize;

            lastSize = inputSize % blockSize;
            if (lastSize == 0)
            {
                lastSize = blockSize;
            }
            inputSize -= lastSize;
            if (inputSize)
            {
                /* move all complete blocks to ififo. */
                base->DS = inputSize;
                ltc_hash_move_to_ififo(ctx, input, inputSize, blockSize);

                status = ltc_wait(base);
                if (kStatus_Success != status)
                {
                    return status;
                }

                input += inputSize;
            }
            /* keep last (in)complete 16-bytes block in context struct. */
            /* when 3rd argument of cmac_move_to_ififo() is <= 16 bytes, it only stores the data to context struct */
            status = ltc_hash_move_rest_to_context(ctx, input, lastSize, modeReg, blockSize);
            if (kStatus_Success != status)
            {
                return status;
            }
            inputSize = 0;
        }
        else
        {
            base->DS = sz;
            ltc_hash_move_to_ififo(ctx, input, sz, blockSize);
            inputSize -= sz;
            input += sz;

            status = ltc_wait(base);
            if (kStatus_Success != status)
            {
                return status;
            }

            /* set algorithm state to UPDATE */
            modeReg &= ~LTC_MD_AS_MASK;
            modeReg |= kLTC_ModeUpdate;
            base->MD = modeReg;
        }
    } /* end while */

    return status;
}

/*******************************************************************************
 * HASH Code public
 ******************************************************************************/
status_t LTC_HASH_Init(LTC_Type *base, ltc_hash_ctx_t *ctx, ltc_hash_algo_t algo, const uint8_t *key, uint32_t keySize)
{
    status_t ret;
    ltc_hash_ctx_internal_t *ctxInternal;
    uint32_t i;

    ret = ltc_hash_check_input_args(base, ctx, algo, key, keySize);
    if (ret != kStatus_Success)
    {
        return ret;
    }

    /* set algorithm in context struct for later use */
    ctxInternal = (ltc_hash_ctx_internal_t *)ctx;
    ctxInternal->algo = algo;
    for (i = 0; i < kLTC_HashCtxNumWords; i++)
    {
        ctxInternal->word[i] = 0u;
    }

    /* Steps required only using AES engine */
    if (ltc_hash_alg_is_cmac(algo))
    {
        /* store input key and key length in context struct for later use */
        ctxInternal->word[kLTC_HashCtxKeySize] = keySize;
        ltc_memcpy(&ctxInternal->word[kLTC_HashCtxKeyStartIdx], key, keySize);
    }
    ctxInternal->blksz = 0u;
    for (i = 0; i < sizeof(ctxInternal->blk.w) / sizeof(ctxInternal->blk.w[0]); i++)
    {
        ctxInternal->blk.w[0] = 0u;
    }
    ctxInternal->state = kLTC_HashInit;
    ctxInternal->base = base;

    return kStatus_Success;
}

status_t LTC_HASH_Update(ltc_hash_ctx_t *ctx, const uint8_t *input, uint32_t inputSize)
{
    bool isUpdateState;
    ltc_mode_t modeReg = 0; /* read and write LTC mode register */
    LTC_Type *base;
    status_t status;
    ltc_hash_ctx_internal_t *ctxInternal;
    uint32_t blockSize;

    ctxInternal = (ltc_hash_ctx_internal_t *)ctx;
    status = ltc_hash_check_context(ctxInternal, input);
    if (kStatus_Success != status)
    {
        return status;
    }

    base = ctxInternal->base;
    blockSize = ltc_hash_get_block_size(ctxInternal->algo);
    /* if we are still less than 64 bytes, keep only in context */
    if ((ctxInternal->blksz + inputSize) <= blockSize)
    {
        ltc_memcpy((&ctxInternal->blk.b[0]) + ctxInternal->blksz, input, inputSize);
        ctxInternal->blksz += inputSize;
        return status;
    }
    else
    {
        isUpdateState = ctxInternal->state == kLTC_HashUpdate;
        if (ctxInternal->state == kLTC_HashInit)
        {
            /* set LTC mode register to INITIALIZE job */
            ltc_hash_engine_init(ctxInternal);

#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
            if (ltc_hash_alg_is_cmac(ctxInternal->algo))
            {
#endif /* FSL_FEATURE_LTC_HAS_SHA */
                ctxInternal->state = kLTC_HashUpdate;
                isUpdateState = true;
                base->DS = 0u;
                status = ltc_wait(base);
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
            }
            else
            {
                /* Set the proper block and algorithm mode. */
                modeReg = ltc_hash_algo2mode(ctxInternal->algo, kLTC_ModeInit, NULL);
                base->MD = modeReg;

                ctxInternal->state = kLTC_HashUpdate;
                status = ltc_hash_process_input_data(ctxInternal, input, inputSize, modeReg);
                ltc_hash_save_context(ctxInternal);
            }
#endif /* FSL_FEATURE_LTC_HAS_SHA */
        }
        else if (isUpdateState)
        {
            /* restore LTC context from context struct */
            ltc_hash_restore_context(ctxInternal);
        }
        else
        {
            /* nothing special at this place */
        }
    }

    if (kStatus_Success != status)
    {
        return status;
    }

    if (isUpdateState)
    {
        /* set LTC mode register to UPDATE job */
        ltc_hash_prepare_context_switch(base);
        base->CW = kLTC_ClearDataSize;
        modeReg = ltc_hash_algo2mode(ctxInternal->algo, kLTC_ModeUpdate, NULL);
        base->MD = modeReg;

        /* process input data and save LTC context to context structure */
        status = ltc_hash_process_input_data(ctxInternal, input, inputSize, modeReg);
        ltc_hash_save_context(ctxInternal);
    }
    ltc_clear_all(base, false);
    return status;
}

status_t LTC_HASH_Finish(ltc_hash_ctx_t *ctx, uint8_t *output, uint32_t *outputSize)
{
    ltc_mode_t modeReg; /* read and write LTC mode register */
    LTC_Type *base;
    uint32_t algOutSize = 0;
    status_t status;
    ltc_hash_ctx_internal_t *ctxInternal;
    uint32_t *ctxW;
    uint32_t i;

    ctxInternal = (ltc_hash_ctx_internal_t *)ctx;
    status = ltc_hash_check_context(ctxInternal, output);
    if (kStatus_Success != status)
    {
        return status;
    }

    base = ctxInternal->base;
    ltc_hash_prepare_context_switch(base);

    base->CW = kLTC_ClearDataSize;
    if (ctxInternal->state == kLTC_HashInit)
    {
        ltc_hash_engine_init(ctxInternal);
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
        if (ltc_hash_alg_is_cmac(ctxInternal->algo))
        {
#endif /* FSL_FEATURE_LTC_HAS_SHA */
            base->DS = 0u;
            status = ltc_wait(base);
            if (kStatus_Success != status)
            {
                return status;
            }
            modeReg = ltc_hash_algo2mode(ctxInternal->algo, kLTC_ModeFinalize, &algOutSize);
#if defined(FSL_FEATURE_LTC_HAS_SHA) && FSL_FEATURE_LTC_HAS_SHA
        }
        else
        {
            modeReg = ltc_hash_algo2mode(ctxInternal->algo, kLTC_ModeInitFinal, &algOutSize);
        }
#endif /* FSL_FEATURE_LTC_HAS_SHA */
        base->MD = modeReg;
    }
    else
    {
        modeReg = ltc_hash_algo2mode(ctxInternal->algo, kLTC_ModeFinalize, &algOutSize);
        base->MD = modeReg;

        /* restore LTC context from context struct */
        ltc_hash_restore_context(ctxInternal);
    }

    /* flush message last incomplete block, if there is any, or write zero to data size register. */
    base->DS = ctxInternal->blksz;
    ltc_hash_block_to_ififo(base, &ctxInternal->blk, ctxInternal->blksz, ltc_hash_get_block_size(ctxInternal->algo));
    /* Wait for finish of the encryption */
    status = ltc_wait(base);

    if (outputSize)
    {
        if (algOutSize < *outputSize)
        {
            *outputSize = algOutSize;
        }
        else
        {
            algOutSize = *outputSize;
        }
    }

    ltc_get_context(base, &output[0], algOutSize, 0u);

    ctxW = (uint32_t *)ctx;
    for (i = 0; i < LTC_HASH_CTX_SIZE; i++)
    {
        ctxW[i] = 0u;
    }

    ltc_clear_all(base, false);
    return status;
}

status_t LTC_HASH(LTC_Type *base,
                  ltc_hash_algo_t algo,
                  const uint8_t *input,
                  uint32_t inputSize,
                  const uint8_t *key,
                  uint32_t keySize,
                  uint8_t *output,
                  uint32_t *outputSize)
{
    status_t status;
    ltc_hash_ctx_t ctx;

    status = LTC_HASH_Init(base, &ctx, algo, key, keySize);
    if (status != kStatus_Success)
    {
        return status;
    }
    status = LTC_HASH_Update(&ctx, input, inputSize);
    if (status != kStatus_Success)
    {
        return status;
    }
    status = LTC_HASH_Finish(&ctx, output, outputSize);
    return status;
}

/*******************************************************************************
 * PKHA Code static
 ******************************************************************************/
#if defined(FSL_FEATURE_LTC_HAS_PKHA) && FSL_FEATURE_LTC_HAS_PKHA
static status_t ltc_pkha_clear_regabne(LTC_Type *base, bool A, bool B, bool N, bool E)
{
    ltc_mode_t mode;

    /* Set the PKHA algorithm and the appropriate function. */
    mode = (uint32_t)kLTC_AlgorithmPKHA | 1U;

    /* Set ram area to clear. Clear all. */
    if (A)
    {
        mode |= 1U << 19U;
    }
    if (B)
    {
        mode |= 1U << 18U;
    }
    if (N)
    {
        mode |= 1U << 16U;
    }
    if (E)
    {
        mode |= 1U << 17U;
    }

    /* Write the mode register to the hardware.
     * NOTE: This will begin the operation. */
    base->MDPK = mode;

    /* Wait for 'done' */
    return ltc_wait(base);
}

static void ltc_pkha_default_parms(ltc_pkha_mode_params_t *params)
{
    params->func = (ltc_pkha_func_t)0;
    params->arithType = kLTC_PKHA_IntegerArith;
    params->montFormIn = kLTC_PKHA_NormalValue;
    params->montFormOut = kLTC_PKHA_NormalValue;
    params->srcReg = kLTC_PKHA_RegAll;
    params->srcQuad = kLTC_PKHA_Quad0;
    params->dstReg = kLTC_PKHA_RegAll;
    params->dstQuad = kLTC_PKHA_Quad0;
    params->equalTime = kLTC_PKHA_NoTimingEqualized;
    params->r2modn = kLTC_PKHA_CalcR2;
}

static void ltc_pkha_write_word(LTC_Type *base, ltc_pkha_reg_area_t reg, uint8_t index, uint32_t data)
{
    switch (reg)
    {
        case kLTC_PKHA_RegA:
            base->PKA[index] = data;
            break;

        case kLTC_PKHA_RegB:
            base->PKB[index] = data;
            break;

        case kLTC_PKHA_RegN:
            base->PKN[index] = data;
            break;

        case kLTC_PKHA_RegE:
            base->PKE[index] = data;
            break;

        default:
            break;
    }
}

static uint32_t ltc_pkha_read_word(LTC_Type *base, ltc_pkha_reg_area_t reg, uint8_t index)
{
    uint32_t retval;

    switch (reg)
    {
        case kLTC_PKHA_RegA:
            retval = base->PKA[index];
            break;

        case kLTC_PKHA_RegB:
            retval = base->PKB[index];
            break;

        case kLTC_PKHA_RegN:
            retval = base->PKN[index];
            break;

        case kLTC_PKHA_RegE:
            retval = base->PKE[index];
            break;

        default:
            retval = 0;
            break;
    }
    return retval;
}

static status_t ltc_pkha_write_reg(
    LTC_Type *base, ltc_pkha_reg_area_t reg, uint8_t quad, const uint8_t *data, uint16_t dataSize)
{
    /* Select the word-based start index for each quadrant of 64 bytes. */
    uint8_t startIndex = (quad * 16u);
    uint32_t outWord;

    while (dataSize > 0)
    {
        if (dataSize >= sizeof(uint32_t))
        {
            ltc_pkha_write_word(base, reg, startIndex++, ltc_get_word_from_unaligned(data));
            dataSize -= sizeof(uint32_t);
            data += sizeof(uint32_t);
        }
        else /* (dataSize > 0) && (dataSize < 4) */
        {
            outWord = 0;
            ltc_memcpy(&outWord, data, dataSize);
            ltc_pkha_write_word(base, reg, startIndex, outWord);
            dataSize = 0;
        }
    }

    return kStatus_Success;
}

static void ltc_pkha_read_reg(LTC_Type *base, ltc_pkha_reg_area_t reg, uint8_t quad, uint8_t *data, uint16_t dataSize)
{
    /* Select the word-based start index for each quadrant of 64 bytes. */
    uint8_t startIndex = (quad * 16u);
    uint16_t calcSize;
    uint32_t word;

    while (dataSize > 0)
    {
        word = ltc_pkha_read_word(base, reg, startIndex++);

        calcSize = (dataSize >= sizeof(uint32_t)) ? sizeof(uint32_t) : dataSize;
        ltc_memcpy(data, &word, calcSize);

        data += calcSize;
        dataSize -= calcSize;
    }
}

static void ltc_pkha_init_data(LTC_Type *base,
                               const uint8_t *A,
                               uint16_t sizeA,
                               const uint8_t *B,
                               uint16_t sizeB,
                               const uint8_t *N,
                               uint16_t sizeN,
                               const uint8_t *E,
                               uint16_t sizeE)
{
    uint32_t clearMask = kLTC_ClearMode; /* clear Mode Register */

    /* Clear internal register states. */
    if (sizeA)
    {
        clearMask |= kLTC_ClearPkhaSizeA;
    }
    if (sizeB)
    {
        clearMask |= kLTC_ClearPkhaSizeB;
    }
    if (sizeN)
    {
        clearMask |= kLTC_ClearPkhaSizeN;
    }
    if (sizeE)
    {
        clearMask |= kLTC_ClearPkhaSizeE;
    }

    base->CW = clearMask;
    base->STA = kLTC_StatusDoneIsr;
    ltc_pkha_clear_regabne(base, A, B, N, E);

    /* Write register sizes. */
    /* Write modulus (N) and A and B register arguments. */
    if (sizeN)
    {
        base->PKNSZ = sizeN;
        if (N)
        {
            ltc_pkha_write_reg(base, kLTC_PKHA_RegN, 0, N, sizeN);
        }
    }

    if (sizeA)
    {
        base->PKASZ = sizeA;
        if (A)
        {
            ltc_pkha_write_reg(base, kLTC_PKHA_RegA, 0, A, sizeA);
        }
    }

    if (sizeB)
    {
        base->PKBSZ = sizeB;
        if (B)
        {
            ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 0, B, sizeB);
        }
    }

    if (sizeE)
    {
        base->PKESZ = sizeE;
        if (E)
        {
            ltc_pkha_write_reg(base, kLTC_PKHA_RegE, 0, E, sizeE);
        }
    }
}

static void ltc_pkha_mode_set_src_reg_copy(ltc_mode_t *outMode, ltc_pkha_reg_area_t reg)
{
    int i = 0;

    do
    {
        reg = (ltc_pkha_reg_area_t)(((uint32_t)reg) >> 1u);
        i++;
    } while (reg);

    i = 4 - i;
    /* Source register must not be E. */
    if (i != 2)
    {
        *outMode |= ((uint32_t)i << 17u);
    }
}

static void ltc_pkha_mode_set_dst_reg_copy(ltc_mode_t *outMode, ltc_pkha_reg_area_t reg)
{
    int i = 0;

    do
    {
        reg = (ltc_pkha_reg_area_t)(((uint32_t)reg) >> 1u);
        i++;
    } while (reg);

    i = 4 - i;
    *outMode |= ((uint32_t)i << 10u);
}

static void ltc_pkha_mode_set_src_seg_copy(ltc_mode_t *outMode, const ltc_pkha_quad_area_t quad)
{
    *outMode |= ((uint32_t)quad << 8u);
}

static void ltc_pkha_mode_set_dst_seg_copy(ltc_mode_t *outMode, const ltc_pkha_quad_area_t quad)
{
    *outMode |= ((uint32_t)quad << 6u);
}

/*!
 * @brief Starts the PKHA operation.
 *
 * This function starts an operation configured by the params parameter.
 *
 * @param base LTC peripheral base address
 * @param params Configuration structure containing all settings required for PKHA operation.
 */
static status_t ltc_pkha_init_mode(LTC_Type *base, const ltc_pkha_mode_params_t *params)
{
    ltc_mode_t modeReg;
    status_t retval;

    /* Set the PKHA algorithm and the appropriate function. */
    modeReg = kLTC_AlgorithmPKHA;
    modeReg |= (uint32_t)params->func;

    if ((params->func == kLTC_PKHA_CopyMemSizeN) || (params->func == kLTC_PKHA_CopyMemSizeSrc))
    {
        /* Set source and destination registers and quads. */
        ltc_pkha_mode_set_src_reg_copy(&modeReg, params->srcReg);
        ltc_pkha_mode_set_dst_reg_copy(&modeReg, params->dstReg);
        ltc_pkha_mode_set_src_seg_copy(&modeReg, params->srcQuad);
        ltc_pkha_mode_set_dst_seg_copy(&modeReg, params->dstQuad);
    }
    else
    {
        /* Set the arithmetic type - integer or binary polynomial (F2m). */
        modeReg |= ((uint32_t)params->arithType << 17u);

        /* Set to use Montgomery form of inputs and/or outputs. */
        modeReg |= ((uint32_t)params->montFormIn << 19u);
        modeReg |= ((uint32_t)params->montFormOut << 18u);

        /* Set to use pre-computed R2modN */
        modeReg |= ((uint32_t)params->r2modn << 16u);
    }

    modeReg |= ((uint32_t)params->equalTime << 10u);

    /* Write the mode register to the hardware.
     * NOTE: This will begin the operation. */
    base->MDPK = modeReg;

    retval = ltc_wait(base);
    return (retval);
}

static status_t ltc_pkha_modR2(
    LTC_Type *base, const uint8_t *N, uint16_t sizeN, uint8_t *result, uint16_t *resultSize, ltc_pkha_f2m_t arithType)
{
    status_t status;
    ltc_pkha_mode_params_t params;

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithModR2;
    params.arithType = arithType;

    ltc_pkha_init_data(base, NULL, 0, NULL, 0, N, sizeN, NULL, 0);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    return status;
}

static status_t ltc_pkha_modmul(LTC_Type *base,
                                const uint8_t *A,
                                uint16_t sizeA,
                                const uint8_t *B,
                                uint16_t sizeB,
                                const uint8_t *N,
                                uint16_t sizeN,
                                uint8_t *result,
                                uint16_t *resultSize,
                                ltc_pkha_f2m_t arithType,
                                ltc_pkha_montgomery_form_t montIn,
                                ltc_pkha_montgomery_form_t montOut,
                                ltc_pkha_timing_t equalTime)
{
    ltc_pkha_mode_params_t params;
    status_t status;

    if (arithType == kLTC_PKHA_IntegerArith)
    {
        if (LTC_PKHA_CompareBigNum(A, sizeA, N, sizeN) >= 0)
        {
            return (kStatus_InvalidArgument);
        }

        if (LTC_PKHA_CompareBigNum(B, sizeB, N, sizeN) >= 0)
        {
            return (kStatus_InvalidArgument);
        }
    }

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithModMul;
    params.arithType = arithType;
    params.montFormIn = montIn;
    params.montFormOut = montOut;
    params.equalTime = equalTime;

    ltc_pkha_init_data(base, A, sizeA, B, sizeB, N, sizeN, NULL, 0);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    return status;
}

/*******************************************************************************
 * PKHA Code public
 ******************************************************************************/
int LTC_PKHA_CompareBigNum(const uint8_t *a, size_t sizeA, const uint8_t *b, size_t sizeB)
{
    int retval;

    /* skip zero msbytes - integer a */
    if (sizeA)
    {
        while (0u == a[sizeA - 1])
        {
            sizeA--;
        }
    }

    /* skip zero msbytes - integer b */
    if (sizeB)
    {
        while (0u == b[sizeB - 1])
        {
            sizeB--;
        }
    }

    if (sizeA > sizeB)
    {
        retval = 1;
    } /* int a has more non-zero bytes, thus it is bigger than b */
    else if (sizeA < sizeB)
    {
        retval = -1;
    } /* int b has more non-zero bytes, thus it is bigger than a */
    else if (sizeA == 0)
    {
        retval = 0;
    } /* sizeA = sizeB = 0 */
    else
    {
        int n;

        n = sizeA - 1;
        /* skip all equal bytes */
        while ((n >= 0) && (a[n] == b[n]))
        {
            n--;
        }
        if (n < 0)
        {
            retval = 0;
        }
        else
        {
            retval = (a[n] > b[n]) ? 1 : -1;
        }
    }
    return (retval);
}

status_t LTC_PKHA_NormalToMontgomery(LTC_Type *base,
                                     const uint8_t *N,
                                     uint16_t sizeN,
                                     uint8_t *A,
                                     uint16_t *sizeA,
                                     uint8_t *B,
                                     uint16_t *sizeB,
                                     uint8_t *R2,
                                     uint16_t *sizeR2,
                                     ltc_pkha_timing_t equalTime,
                                     ltc_pkha_f2m_t arithType)
{
    status_t status;

    /* need to convert our Integer inputs into Montgomery format */
    if (N && sizeN && R2 && sizeR2)
    {
        /* 1. R2 = MOD_R2(N) */
        status = ltc_pkha_modR2(base, N, sizeN, R2, sizeR2, arithType);
        if (status != kStatus_Success)
        {
            return status;
        }

        /* 2. A(Montgomery) = MOD_MUL_IM_OM(A, R2, N) */
        if (A && sizeA)
        {
            status = ltc_pkha_modmul(base, A, *sizeA, R2, *sizeR2, N, sizeN, A, sizeA, arithType,
                                     kLTC_PKHA_MontgomeryFormat, kLTC_PKHA_MontgomeryFormat, equalTime);
            if (status != kStatus_Success)
            {
                return status;
            }
        }

        /* 2. B(Montgomery) = MOD_MUL_IM_OM(B, R2, N) */
        if (B && sizeB)
        {
            status = ltc_pkha_modmul(base, B, *sizeB, R2, *sizeR2, N, sizeN, B, sizeB, arithType,
                                     kLTC_PKHA_MontgomeryFormat, kLTC_PKHA_MontgomeryFormat, equalTime);
            if (status != kStatus_Success)
            {
                return status;
            }
        }

        ltc_clear_all(base, true);
    }
    else
    {
        status = kStatus_InvalidArgument;
    }

    return status;
}

status_t LTC_PKHA_MontgomeryToNormal(LTC_Type *base,
                                     const uint8_t *N,
                                     uint16_t sizeN,
                                     uint8_t *A,
                                     uint16_t *sizeA,
                                     uint8_t *B,
                                     uint16_t *sizeB,
                                     ltc_pkha_timing_t equalTime,
                                     ltc_pkha_f2m_t arithType)
{
    uint8_t one = 1;
    status_t status = kStatus_InvalidArgument;

    /* A = MOD_MUL_IM_OM(A(Montgomery), 1, N) */
    if (A && sizeA)
    {
        status = ltc_pkha_modmul(base, A, *sizeA, &one, sizeof(one), N, sizeN, A, sizeA, arithType,
                                 kLTC_PKHA_MontgomeryFormat, kLTC_PKHA_MontgomeryFormat, equalTime);
        if (kStatus_Success != status)
        {
            return status;
        }
    }

    /* B = MOD_MUL_IM_OM(B(Montgomery), 1, N) */
    if (B && sizeB)
    {
        status = ltc_pkha_modmul(base, B, *sizeB, &one, sizeof(one), N, sizeN, B, sizeB, arithType,
                                 kLTC_PKHA_MontgomeryFormat, kLTC_PKHA_MontgomeryFormat, equalTime);
        if (kStatus_Success != status)
        {
            return status;
        }
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ModAdd(LTC_Type *base,
                         const uint8_t *A,
                         uint16_t sizeA,
                         const uint8_t *B,
                         uint16_t sizeB,
                         const uint8_t *N,
                         uint16_t sizeN,
                         uint8_t *result,
                         uint16_t *resultSize,
                         ltc_pkha_f2m_t arithType)
{
    ltc_pkha_mode_params_t params;
    status_t status;

    if (arithType == kLTC_PKHA_IntegerArith)
    {
        if (LTC_PKHA_CompareBigNum(A, sizeA, N, sizeN) >= 0)
        {
            return (kStatus_InvalidArgument);
        }

        if (LTC_PKHA_CompareBigNum(B, sizeB, N, sizeN) >= 0)
        {
            return (kStatus_InvalidArgument);
        }
    }

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithModAdd;
    params.arithType = arithType;

    ltc_pkha_init_data(base, A, sizeA, B, sizeB, N, sizeN, NULL, 0);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ModSub1(LTC_Type *base,
                          const uint8_t *A,
                          uint16_t sizeA,
                          const uint8_t *B,
                          uint16_t sizeB,
                          const uint8_t *N,
                          uint16_t sizeN,
                          uint8_t *result,
                          uint16_t *resultSize)
{
    ltc_pkha_mode_params_t params;
    status_t status;

    if (LTC_PKHA_CompareBigNum(A, sizeA, N, sizeN) >= 0)
    {
        return (kStatus_InvalidArgument);
    }

    if (LTC_PKHA_CompareBigNum(B, sizeB, N, sizeN) >= 0)
    {
        return (kStatus_InvalidArgument);
    }

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithModSub1;
    ltc_pkha_init_data(base, A, sizeA, B, sizeB, N, sizeN, NULL, 0);

    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ModSub2(LTC_Type *base,
                          const uint8_t *A,
                          uint16_t sizeA,
                          const uint8_t *B,
                          uint16_t sizeB,
                          const uint8_t *N,
                          uint16_t sizeN,
                          uint8_t *result,
                          uint16_t *resultSize)
{
    ltc_pkha_mode_params_t params;
    status_t status;

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithModSub2;

    ltc_pkha_init_data(base, A, sizeA, B, sizeB, N, sizeN, NULL, 0);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ModMul(LTC_Type *base,
                         const uint8_t *A,
                         uint16_t sizeA,
                         const uint8_t *B,
                         uint16_t sizeB,
                         const uint8_t *N,
                         uint16_t sizeN,
                         uint8_t *result,
                         uint16_t *resultSize,
                         ltc_pkha_f2m_t arithType,
                         ltc_pkha_montgomery_form_t montIn,
                         ltc_pkha_montgomery_form_t montOut,
                         ltc_pkha_timing_t equalTime)
{
    status_t status;

    status =
        ltc_pkha_modmul(base, A, sizeA, B, sizeB, N, sizeN, result, resultSize, arithType, montIn, montOut, equalTime);

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ModExp(LTC_Type *base,
                         const uint8_t *A,
                         uint16_t sizeA,
                         const uint8_t *N,
                         uint16_t sizeN,
                         const uint8_t *E,
                         uint16_t sizeE,
                         uint8_t *result,
                         uint16_t *resultSize,
                         ltc_pkha_f2m_t arithType,
                         ltc_pkha_montgomery_form_t montIn,
                         ltc_pkha_timing_t equalTime)
{
    ltc_pkha_mode_params_t params;
    status_t status;

    if (arithType == kLTC_PKHA_IntegerArith)
    {
        if (LTC_PKHA_CompareBigNum(A, sizeA, N, sizeN) >= 0)
        {
            return (kStatus_InvalidArgument);
        }
    }

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithModExp;
    params.arithType = arithType;
    params.montFormIn = montIn;
    params.equalTime = equalTime;

    ltc_pkha_init_data(base, A, sizeA, NULL, 0, N, sizeN, E, sizeE);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ModRed(LTC_Type *base,
                         const uint8_t *A,
                         uint16_t sizeA,
                         const uint8_t *N,
                         uint16_t sizeN,
                         uint8_t *result,
                         uint16_t *resultSize,
                         ltc_pkha_f2m_t arithType)
{
    ltc_pkha_mode_params_t params;
    status_t status;

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithModRed;
    params.arithType = arithType;

    ltc_pkha_init_data(base, A, sizeA, NULL, 0, N, sizeN, NULL, 0);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ModInv(LTC_Type *base,
                         const uint8_t *A,
                         uint16_t sizeA,
                         const uint8_t *N,
                         uint16_t sizeN,
                         uint8_t *result,
                         uint16_t *resultSize,
                         ltc_pkha_f2m_t arithType)
{
    ltc_pkha_mode_params_t params;
    status_t status;

    /* A must be less than N -> LTC_PKHA_CompareBigNum() must return -1 */
    if (arithType == kLTC_PKHA_IntegerArith)
    {
        if (LTC_PKHA_CompareBigNum(A, sizeA, N, sizeN) >= 0)
        {
            return (kStatus_InvalidArgument);
        }
    }

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithModInv;
    params.arithType = arithType;

    ltc_pkha_init_data(base, A, sizeA, NULL, 0, N, sizeN, NULL, 0);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ModR2(
    LTC_Type *base, const uint8_t *N, uint16_t sizeN, uint8_t *result, uint16_t *resultSize, ltc_pkha_f2m_t arithType)
{
    status_t status;
    status = ltc_pkha_modR2(base, N, sizeN, result, resultSize, arithType);
    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_GCD(LTC_Type *base,
                      const uint8_t *A,
                      uint16_t sizeA,
                      const uint8_t *N,
                      uint16_t sizeN,
                      uint8_t *result,
                      uint16_t *resultSize,
                      ltc_pkha_f2m_t arithType)
{
    ltc_pkha_mode_params_t params;
    status_t status;

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithGcd;
    params.arithType = arithType;

    ltc_pkha_init_data(base, A, sizeA, NULL, 0, N, sizeN, NULL, 0);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the result and size from register B0. */
        if (resultSize && result)
        {
            *resultSize = base->PKBSZ;
            /* Read the data from the result register into place. */
            ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, result, *resultSize);
        }
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_PrimalityTest(LTC_Type *base,
                                const uint8_t *A,
                                uint16_t sizeA,
                                const uint8_t *B,
                                uint16_t sizeB,
                                const uint8_t *N,
                                uint16_t sizeN,
                                bool *res)
{
    uint8_t result;
    ltc_pkha_mode_params_t params;
    status_t status;

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithPrimalityTest;
    ltc_pkha_init_data(base, A, sizeA, B, sizeB, N, sizeN, NULL, 0);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the data from the result register into place. */
        ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 0, &result, 1);

        *res = (bool)result;
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ECC_PointAdd(LTC_Type *base,
                               const ltc_pkha_ecc_point_t *A,
                               const ltc_pkha_ecc_point_t *B,
                               const uint8_t *N,
                               const uint8_t *R2modN,
                               const uint8_t *aCurveParam,
                               const uint8_t *bCurveParam,
                               uint8_t size,
                               ltc_pkha_f2m_t arithType,
                               ltc_pkha_ecc_point_t *result)
{
    ltc_pkha_mode_params_t params;
    uint32_t clearMask;
    status_t status;

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithEccAdd;
    params.arithType = arithType;
    params.r2modn = R2modN ? kLTC_PKHA_InputR2 : kLTC_PKHA_CalcR2;

    clearMask = kLTC_ClearMode;

    /* Clear internal register states. */
    clearMask |= kLTC_ClearPkhaSizeA;
    clearMask |= kLTC_ClearPkhaSizeB;
    clearMask |= kLTC_ClearPkhaSizeN;
    clearMask |= kLTC_ClearPkhaSizeE;

    base->CW = clearMask;
    base->STA = kLTC_StatusDoneIsr;
    ltc_pkha_clear_regabne(base, true, true, true, false);

    /* sizeN should be less than 64 bytes. */
    base->PKNSZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegN, 0, N, size);

    base->PKASZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegA, 0, A->X, size);
    ltc_pkha_write_reg(base, kLTC_PKHA_RegA, 1, A->Y, size);
    ltc_pkha_write_reg(base, kLTC_PKHA_RegA, 3, aCurveParam, size);

    base->PKBSZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 0, bCurveParam, size);
    ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 1, B->X, size);
    ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 2, B->Y, size);
    if (R2modN)
    {
        ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 3, R2modN, size);
    }

    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the data from the result register into place. */
        ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 1, result->X, size);
        ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 2, result->Y, size);
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ECC_PointDouble(LTC_Type *base,
                                  const ltc_pkha_ecc_point_t *B,
                                  const uint8_t *N,
                                  const uint8_t *aCurveParam,
                                  const uint8_t *bCurveParam,
                                  uint8_t size,
                                  ltc_pkha_f2m_t arithType,
                                  ltc_pkha_ecc_point_t *result)
{
    ltc_pkha_mode_params_t params;
    uint32_t clearMask;
    status_t status;

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithEccDouble;
    params.arithType = arithType;

    clearMask = kLTC_ClearMode;

    /* Clear internal register states. */
    clearMask |= kLTC_ClearPkhaSizeA;
    clearMask |= kLTC_ClearPkhaSizeB;
    clearMask |= kLTC_ClearPkhaSizeN;
    clearMask |= kLTC_ClearPkhaSizeE;

    base->CW = clearMask;
    base->STA = kLTC_StatusDoneIsr;
    ltc_pkha_clear_regabne(base, true, true, true, false);

    /* sizeN should be less than 64 bytes. */
    base->PKNSZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegN, 0, N, size);

    base->PKASZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegA, 3, aCurveParam, size);

    base->PKBSZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 0, bCurveParam, size);
    ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 1, B->X, size);
    ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 2, B->Y, size);
    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the data from the result register into place. */
        ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 1, result->X, size);
        ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 2, result->Y, size);
    }

    ltc_clear_all(base, true);
    return status;
}

status_t LTC_PKHA_ECC_PointMul(LTC_Type *base,
                               const ltc_pkha_ecc_point_t *A,
                               const uint8_t *E,
                               uint8_t sizeE,
                               const uint8_t *N,
                               const uint8_t *R2modN,
                               const uint8_t *aCurveParam,
                               const uint8_t *bCurveParam,
                               uint8_t size,
                               ltc_pkha_timing_t equalTime,
                               ltc_pkha_f2m_t arithType,
                               ltc_pkha_ecc_point_t *result,
                               bool *infinity)
{
    ltc_pkha_mode_params_t params;
    uint32_t clearMask;
    status_t status;

    ltc_pkha_default_parms(&params);
    params.func = kLTC_PKHA_ArithEccMul;
    params.equalTime = equalTime;
    params.arithType = arithType;
    params.r2modn = R2modN ? kLTC_PKHA_InputR2 : kLTC_PKHA_CalcR2;

    clearMask = kLTC_ClearMode;

    /* Clear internal register states. */
    clearMask |= kLTC_ClearPkhaSizeA;
    clearMask |= kLTC_ClearPkhaSizeB;
    clearMask |= kLTC_ClearPkhaSizeN;
    clearMask |= kLTC_ClearPkhaSizeE;

    base->CW = clearMask;
    base->STA = kLTC_StatusDoneIsr;
    ltc_pkha_clear_regabne(base, true, true, true, true);

    /* sizeN should be less than 64 bytes. */
    base->PKNSZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegN, 0, N, size);

    base->PKESZ = sizeE;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegE, 0, E, sizeE);

    base->PKASZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegA, 0, A->X, size);
    ltc_pkha_write_reg(base, kLTC_PKHA_RegA, 1, A->Y, size);
    ltc_pkha_write_reg(base, kLTC_PKHA_RegA, 3, aCurveParam, size);

    base->PKBSZ = size;
    ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 0, bCurveParam, size);
    if (R2modN)
    {
        ltc_pkha_write_reg(base, kLTC_PKHA_RegB, 1, R2modN, size);
    }

    status = ltc_pkha_init_mode(base, &params);

    if (status == kStatus_Success)
    {
        /* Read the data from the result register into place. */
        ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 1, result->X, size);
        ltc_pkha_read_reg(base, kLTC_PKHA_RegB, 2, result->Y, size);

        if (infinity)
        {
            *infinity = (bool)(base->STA & kLTC_StatusPublicKeyOpZero);
        }
    }

    ltc_clear_all(base, true);
    return status;
}

#endif /* FSL_FEATURE_LTC_HAS_PKHA */
