/*
 * Copyright (c) 2016, Freescale Semiconductor, Inc.
 * Copyright 2016-2019 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_spifi.h"

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

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

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

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

/* Array of SPIFI peripheral base address. */
static SPIFI_Type *const s_spifiBases[] = SPIFI_BASE_PTRS;

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* Array of SPIFI clock name. */
static const clock_ip_name_t s_spifiClock[] = SPIFI_CLOCKS;
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

#if !(defined(FSL_FEATURE_SPIFI_HAS_NO_RESET) && FSL_FEATURE_SPIFI_HAS_NO_RESET)
static const reset_ip_name_t s_spifiResets[] = SPIFI_RSTS;
#endif

/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * brief Get the SPIFI instance from peripheral base address.
 *
 * param base SPIFI peripheral base address.
 * return SPIFI instance.
 */
uint32_t SPIFI_GetInstance(SPIFI_Type *base)
{
    uint32_t instance;

    /* Find the instance index from base address mappings. */
    for (instance = 0; instance < ARRAY_SIZE(s_spifiBases); instance++)
    {
        if (s_spifiBases[instance] == base)
        {
            break;
        }
    }

    assert(instance < ARRAY_SIZE(s_spifiBases));

    return instance;
}

/*!
 * brief Get SPIFI default configure settings.
 *
 * param config  SPIFI config structure pointer.
 */
void SPIFI_GetDefaultConfig(spifi_config_t *config)
{
    assert(config != NULL);

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

    config->timeout              = 0xFFFFU;
    config->csHighTime           = 0xFU;
    config->disablePrefetch      = false;
    config->disableCachePrefech  = false;
    config->isFeedbackClock      = true;
    config->spiMode              = kSPIFI_SPISckLow;
    config->isReadFullClockCycle = true;
    config->dualMode             = kSPIFI_QuadMode;
}

/*!
 * brief Initializes the SPIFI with the user configuration structure.
 *
 * This function configures the SPIFI module with the user-defined configuration.
 *
 * param base     SPIFI peripheral base address.
 * param config   The pointer to the configuration structure.
 */
void SPIFI_Init(SPIFI_Type *base, const spifi_config_t *config)
{
    assert(config != NULL);

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Enable the SAI clock */
    CLOCK_EnableClock(s_spifiClock[SPIFI_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

#if !(defined(FSL_FEATURE_SPIFI_HAS_NO_RESET) && FSL_FEATURE_SPIFI_HAS_NO_RESET)
    RESET_PeripheralReset(s_spifiResets[SPIFI_GetInstance(base)]);
#endif

    /* Reset the Command register */
    SPIFI_ResetCommand(base);

    /* Set time delay parameter */
    base->CTRL = SPIFI_CTRL_TIMEOUT(config->timeout) | SPIFI_CTRL_CSHIGH(config->csHighTime) |
                 SPIFI_CTRL_D_PRFTCH_DIS(config->disablePrefetch) | SPIFI_CTRL_MODE3(config->spiMode) |
                 SPIFI_CTRL_PRFTCH_DIS(config->disableCachePrefech) | SPIFI_CTRL_DUAL(config->dualMode) |
                 SPIFI_CTRL_RFCLK(config->isReadFullClockCycle) | SPIFI_CTRL_FBCLK(config->isFeedbackClock);
}

/*!
 * brief Deinitializes the SPIFI regions.
 *
 * param base     SPIFI peripheral base address.
 */
void SPIFI_Deinit(SPIFI_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Enable the SAI clock */
    CLOCK_DisableClock(s_spifiClock[SPIFI_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

/*!
 * brief Set SPIFI flash command.
 *
 * param base     SPIFI peripheral base address.
 * param cmd      SPIFI command structure pointer.
 */
void SPIFI_SetCommand(SPIFI_Type *base, spifi_command_t *cmd)
{
    /* If SPIFI in memory mode, call reset function to abort memory mode */
    if ((SPIFI_GetStatusFlag(base) & SPIFI_STAT_MCINIT_MASK) != 0x00U)
    {
        SPIFI_ResetCommand(base);
    }

    /* Wait for other command finished */
    while ((SPIFI_GetStatusFlag(base) & SPIFI_STAT_CMD_MASK) != 0x00U)
    {
    }

    base->CMD = SPIFI_CMD_DATALEN(cmd->dataLen) | SPIFI_CMD_POLL(cmd->isPollMode) | SPIFI_CMD_DOUT(cmd->direction) |
                SPIFI_CMD_INTLEN(cmd->intermediateBytes) | SPIFI_CMD_FIELDFORM(cmd->format) |
                SPIFI_CMD_FRAMEFORM(cmd->type) | SPIFI_CMD_OPCODE(cmd->opcode);
}

/*!
 * brief Set SPIFI flash AHB read command.
 *
 * Call this function means SPIFI enters to memory mode, while users need to use command, a SPIFI_ResetCommand shall
 * be called.
 *
 * param base     SPIFI peripheral base address.
 * param cmd      SPIFI command structure pointer.
 */
void SPIFI_SetMemoryCommand(SPIFI_Type *base, spifi_command_t *cmd)
{
    /* Wait for the CMD flag be 0 */
    while ((SPIFI_GetStatusFlag(base) & SPIFI_STAT_CMD_MASK) != 0x00U)
    {
    }

    base->MCMD = SPIFI_MCMD_POLL(0U) | SPIFI_MCMD_DOUT(0U) | SPIFI_MCMD_INTLEN(cmd->intermediateBytes) |
                 SPIFI_MCMD_FIELDFORM(cmd->format) | SPIFI_MCMD_FRAMEFORM(cmd->type) | SPIFI_MCMD_OPCODE(cmd->opcode);

    /* Wait for the command written */
    while ((base->STAT & SPIFI_STAT_MCINIT_MASK) == 0x00U)
    {
    }
}

/*!
 * brief Write a halfword data in address of SPIFI.
 *
 * Users can write a halfword data into SPIFI address.
 *
 * param base     SPIFI peripheral base address.
 * param data     Data need be write.
 */
void SPIFI_WriteDataHalfword(SPIFI_Type *base, uint16_t data)
{
    volatile uint8_t *dataReg = ((volatile uint8_t *)(&(base->DATA)));

    *dataReg = ((uint8_t)data & 0xFFU);
    dataReg++;
    *dataReg = (((uint8_t)(data >> 8U)) & 0xFFU);
}

/*!
 * brief Read a halfword data from serial flash.
 *
 * param base     SPIFI peripheral base address.
 * return Data input from flash.
 */
uint16_t SPIFI_ReadDataHalfword(SPIFI_Type *base)
{
    uint16_t val              = 0;
    volatile uint8_t *dataReg = ((volatile uint8_t *)(&(base->DATA)));
    uint8_t temp1             = *dataReg;
    uint8_t temp2             = *(dataReg + 0x01U);

    val = temp1 | ((uint16_t)temp2) << 8U;

    return val;
}
