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

#include "fsl_pgmc.h"

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

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

/*!
 * @brief The structure of MIF signal.
 */
typedef struct
{
    __IO uint32_t SIGNAL; /*!< MIF MLPL control of each signal. */
    __IO uint32_t DELAY;  /*!< MIF Delay of each signal */
    uint32_t RESERVED[2];
} PGMC_MIF_SIGNAL_Type;

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

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

/*!
 * brief Makes the BPC module controlled by the target CPU power mode(Such as Wait mode).
 *
 * This function makes the module controlled by four typical CPU power modes, It also configs the resource domain and
 * set memory low power level.
 *
 * param base PGMC basic power controller base address.
 * param mode Target CPU power mode.
 * param option The pointer of @ref pgmc_bpc_cpu_power_mode_option_t structure.
 */
void PGMC_BPC_ControlPowerDomainByCpuPowerMode(PGMC_BPC_Type *base,
                                               pgmc_cpu_mode_t mode,
                                               const pgmc_bpc_cpu_power_mode_option_t *option)
{
    assert(option != NULL);

    uint32_t tmp32 = base->BPC_SSAR_SAVE_CTRL;

    base->BPC_MODE = PGMC_BPC_BPC_MODE_DOMAIN_ASSIGN(option->assignDomain) |
                     PGMC_BPC_BPC_MODE_CTRL_MODE(kPGMC_ControlledByCpuPowerMode);
    switch (mode)
    {
        case kPGMC_RunMode:
            tmp32 |= PGMC_BPC_BPC_SSAR_SAVE_CTRL_SAVE_AT_RUN_MASK;
            break;
        case kPGMC_WaitMode:
            if (option->powerOff)
            {
                base->BPC_POWER_CTRL |= PGMC_BPC_BPC_POWER_CTRL_PWR_OFF_AT_WAIT_MASK;
            }
            tmp32 |= PGMC_BPC_BPC_SSAR_SAVE_CTRL_SAVE_AT_WAIT_MASK;
            break;
        case kPGMC_StopMode:
            if (option->powerOff)
            {
                base->BPC_POWER_CTRL |= PGMC_BPC_BPC_POWER_CTRL_PWR_OFF_AT_STOP_MASK;
            }
            tmp32 |= PGMC_BPC_BPC_SSAR_SAVE_CTRL_SAVE_AT_SUSPEND_MASK;
            break;
        case kPGMC_SuspendMode:
            if (option->powerOff)
            {
                base->BPC_POWER_CTRL |= PGMC_BPC_BPC_POWER_CTRL_PWR_OFF_AT_SUSPEND_MASK;
            }
            tmp32 |= PGMC_BPC_BPC_SSAR_SAVE_CTRL_SAVE_AT_SUSPEND_MASK;
            break;
        default:
            assert(false);
            break;
    }

    if (option->stateSave)
    {
        base->BPC_SSAR_SAVE_CTRL = tmp32;
    }
}

/*!
 * brief Makes the BPC module controlled by the target set points.
 *
 * This function makes the module controlled by specific set point, It also supports set memory lowe power level.
 *
 * note When setting more than one set point, use "|" between the map values in @ref _pgmc_setpoint_map.
 *
 * param base PGMC basic power controller base address.
 * param setPointMap Should be the OR'ed value of @ref _pgmc_setpoint_map.
 * param option The pointer of @ref pgmc_bpc_setpoint_mode_option_t structure.
 */
void PGMC_BPC_ControlPowerDomainBySetPointMode(PGMC_BPC_Type *base,
                                               uint32_t setPointMap,
                                               const pgmc_bpc_setpoint_mode_option_t *option)
{
    assert(option != NULL);

    setPointMap &= 0xFFFFU;

    base->BPC_MODE = PGMC_BPC_BPC_MODE_CTRL_MODE(kPGMC_ControlledBySetPoint);
    if (option->powerOff)
    {
        base->BPC_POWER_CTRL |= PGMC_BPC_BPC_POWER_CTRL_PWR_OFF_AT_SP(setPointMap);
    }

    if (option->stateSave)
    {
        base->BPC_SSAR_SAVE_CTRL = PGMC_BPC_BPC_SSAR_SAVE_CTRL_SAVE_AT_SP(setPointMap);
    }
}

/*!
 * brief Controls the selected power domain by software mode.
 *
 * note The function is used to control power domain when the CPU is in RUN mode.
 *
 * param base PGMC basic power controller base address.
 * param powerOff. Power On/Off power domain in software mode.
 *                  - \b true Power off the power domain in software mode.
 *                  - \b false Power on the power domain in software mode.
 */
void PGMC_BPC_ControlPowerDomainBySoftwareMode(PGMC_BPC_Type *base, bool powerOff)
{
    if (powerOff)
    {
        base->BPC_POWER_CTRL |= (PGMC_BPC_BPC_POWER_CTRL_PSW_OFF_SOFT_MASK | PGMC_BPC_BPC_POWER_CTRL_ISO_ON_SOFT_MASK);
    }
    else
    {
        base->BPC_POWER_CTRL |= (PGMC_BPC_BPC_POWER_CTRL_PSW_ON_SOFT_MASK | PGMC_BPC_BPC_POWER_CTRL_ISO_OFF_SOFT_MASK);
    }
}

/*!
 * brief Powers off the CPC core module by the target CPU power mode(Such as Wait mode).
 *
 * param base CPC CORE module base address.
 * param mode Target CPU power mode.
 */
void PGMC_CPC_CORE_PowerOffByCpuPowerMode(PGMC_CPC_Type *base, pgmc_cpu_mode_t mode)
{
    base->CPC_CORE_MODE = PGMC_CPC_CPC_CORE_MODE_CTRL_MODE(kPGMC_ControlledByCpuPowerMode);
    switch (mode)
    {
        case kPGMC_RunMode:
            break;
        case kPGMC_WaitMode:
            base->CPC_CORE_POWER_CTRL |= PGMC_CPC_CPC_CORE_POWER_CTRL_PWR_OFF_AT_WAIT_MASK;
            break;
        case kPGMC_StopMode:
            base->CPC_CORE_POWER_CTRL |= PGMC_CPC_CPC_CORE_POWER_CTRL_PWR_OFF_AT_STOP_MASK;
            break;
        case kPGMC_SuspendMode:
            base->CPC_CORE_POWER_CTRL |= PGMC_CPC_CPC_CORE_POWER_CTRL_PWR_OFF_AT_SUSPEND_MASK;
            break;
        default:
            assert(false);
            break;
    }
}

/*!
 * brief Makes the CPC CACHE module controlled by the target CPU power mode(Such as Wait mode).
 *
 * This function makes the module controlled by four typical CPU power modes, it also can set memory low power level.
 *
 * param base CPC CACHE module base address.
 * param mode Target CPU power mode.
 * param memoryLowPowerLevel Memory low power level.
 */
void PGMC_CPC_CACHE_ControlByCpuPowerMode(PGMC_CPC_Type *base,
                                          pgmc_cpu_mode_t mode,
                                          pgmc_memory_low_power_level_t memoryLowPowerLevel)
{
    uint32_t temp32;

    base->CPC_CACHE_MODE = PGMC_CPC_CPC_CACHE_MODE_CTRL_MODE(kPGMC_ControlledByCpuPowerMode);
    temp32               = base->CPC_CACHE_CM_CTRL;
    switch (mode)
    {
        case kPGMC_RunMode:
            temp32 &= ~PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_AT_RUN_MASK;
            base->CPC_CACHE_CM_CTRL = temp32 | PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_AT_RUN(memoryLowPowerLevel);
            break;
        case kPGMC_WaitMode:
            temp32 &= ~PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_AT_WAIT_MASK;
            base->CPC_CACHE_CM_CTRL = temp32 | PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_AT_WAIT(memoryLowPowerLevel);
            break;
        case kPGMC_StopMode:
            temp32 &= ~PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_AT_STOP_MASK;
            base->CPC_CACHE_CM_CTRL = temp32 | PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_AT_STOP(memoryLowPowerLevel);
            break;
        case kPGMC_SuspendMode:
            temp32 &= ~PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_AT_SUSPEND_MASK;
            base->CPC_CACHE_CM_CTRL = temp32 | PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_AT_SUSPEND(memoryLowPowerLevel);
            break;
        default:
            assert(false);
            break;
    }
}

/*!
 * brief Makes the CPC CACHE module controlled by the target set points.
 *
 * This function makes the module controlled by specific set point, It also supports set memory lowe power level.
 *
 * note When setting more than one set point, use "|" between the map values in @ref _pgmc_setpoint_map.
 *
 * param base CPC CACHE module base address.
 * param setPointMap Should be the OR'ed value of @ref _pgmc_setpoint_map.
 * param memoryLowPowerLevel Memory low power level.
 */
void PGMC_CPC_CACHE_ControlBySetPointMode(PGMC_CPC_Type *base,
                                          uint32_t setPointMap,
                                          pgmc_memory_low_power_level_t memoryLowPowerLevel)
{
    uint32_t setPointIndex             = 0UL;
    uint32_t tmp32                     = 0UL;
    uint32_t regIndex                  = 0UL;
    volatile uint32_t *ptrMemSpCtrlReg = NULL;

    setPointMap &= 0xFFFFU;

    base->CPC_CACHE_MODE = PGMC_CPC_CPC_CACHE_MODE_CTRL_MODE(kPGMC_ControlledBySetPoint);

    ptrMemSpCtrlReg = &(base->CPC_CACHE_SP_CTRL_0);
    for (regIndex = 0UL; regIndex < 2UL; regIndex++)
    {
        ptrMemSpCtrlReg += regIndex;
        tmp32 = *ptrMemSpCtrlReg;
        for (setPointIndex = 0UL; setPointIndex < 8UL; setPointIndex++)
        {
            if (0UL != (setPointMap & (1UL << ((regIndex * 8UL) + setPointIndex))))
            {
                tmp32 &= ~((uint32_t)PGMC_CPC_CPC_CACHE_SP_CTRL_0_MLPL_AT_SP0_MASK << (setPointIndex * 4U));
                tmp32 |= ((uint32_t)memoryLowPowerLevel << (setPointIndex * 4U));
            }
        }
        *ptrMemSpCtrlReg = tmp32;
    }
}

/*!
 * brief Requests CPC cache module's memory low power level change by software mode.
 *
 * note If request memory low power level change, must wait the MLPL transition complete.
 *
 * param base CPC LMEM module base address.
 */
void PGMC_CPC_CACHE_TriggerMLPLSoftwareChange(PGMC_CPC_Type *base)
{
    base->CPC_CACHE_CM_CTRL |= PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_SOFT_MASK;

    /* If request software change, check the MLPL transition status. */
    while (0UL != ((base->CPC_CACHE_CM_CTRL) & PGMC_CPC_CPC_CACHE_CM_CTRL_MLPL_SOFT_MASK))
    {
    }
}

/*!
 * brief Makes the CPC LMEM module controlled by the target CPU power mode(Such as Wait mode).
 *
 * This function makes the module controlled by four typical CPU power modes, it also can set memory low power level.
 *
 * param base CPC LMEM module base address.
 * param mode Target CPU power mode.
 * param memoryLowPowerLevel Memory low power level.
 */
void PGMC_CPC_LMEM_ControlByCpuPowerMode(PGMC_CPC_Type *base,
                                         pgmc_cpu_mode_t mode,
                                         pgmc_memory_low_power_level_t memoryLowPowerLevel)
{
    uint32_t temp32;

    base->CPC_LMEM_MODE = PGMC_CPC_CPC_LMEM_MODE_CTRL_MODE(kPGMC_ControlledByCpuPowerMode);

    temp32 = base->CPC_LMEM_CM_CTRL;
    switch (mode)
    {
        case kPGMC_RunMode:
            temp32 &= ~PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_AT_RUN_MASK;
            base->CPC_LMEM_CM_CTRL = temp32 | PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_AT_RUN(memoryLowPowerLevel);
            break;
        case kPGMC_WaitMode:
            temp32 &= ~PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_AT_WAIT_MASK;
            base->CPC_LMEM_CM_CTRL = temp32 | PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_AT_WAIT(memoryLowPowerLevel);
            break;
        case kPGMC_StopMode:
            temp32 &= ~PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_AT_STOP_MASK;
            base->CPC_LMEM_CM_CTRL = temp32 | PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_AT_STOP(memoryLowPowerLevel);
            break;
        case kPGMC_SuspendMode:
            temp32 &= ~PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_AT_SUSPEND_MASK;
            base->CPC_LMEM_CM_CTRL = temp32 | PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_AT_SUSPEND(memoryLowPowerLevel);
            break;
        default:
            assert(false);
            break;
    }
}

/*!
 * brief Makes the CPC LMEM module controlled by the target set points.
 *
 * This function makes the module controlled by specific set point, It also supports set memory lowe power level.
 *
 * note When setting more than one set point, use "|" between the map values in @ref _pgmc_setpoint_map.
 *
 * param base CPC LMEM module base address.
 * param setPointMap Should be the OR'ed value of @ref _pgmc_setpoint_map.
 * param memoryLowPowerLevel Memory low power level.
 */
void PGMC_CPC_LMEM_ControlBySetPointMode(PGMC_CPC_Type *base,
                                         uint32_t setPointMap,
                                         pgmc_memory_low_power_level_t memoryLowPowerLevel)
{
    uint32_t setPointIndex             = 0UL;
    uint32_t tmp32                     = 0UL;
    uint32_t regIndex                  = 0UL;
    volatile uint32_t *ptrMemSpCtrlReg = NULL;

    setPointMap &= 0xFFFFU;

    base->CPC_LMEM_MODE = PGMC_CPC_CPC_LMEM_MODE_CTRL_MODE(kPGMC_ControlledBySetPoint);

    ptrMemSpCtrlReg = &(base->CPC_LMEM_SP_CTRL_0);
    for (regIndex = 0UL; regIndex < 2UL; regIndex++)
    {
        ptrMemSpCtrlReg += regIndex;
        tmp32 = *ptrMemSpCtrlReg;
        for (setPointIndex = 0UL; setPointIndex < 8UL; setPointIndex++)
        {
            if (0UL != (setPointMap & (1UL << ((regIndex * 8UL) + setPointIndex))))
            {
                tmp32 &= ~((uint32_t)PGMC_CPC_CPC_LMEM_SP_CTRL_0_MLPL_AT_SP0_MASK << (setPointIndex * 4U));
                tmp32 |= ((uint32_t)memoryLowPowerLevel << (setPointIndex * 4U));
            }
        }
        *ptrMemSpCtrlReg = tmp32;
    }
}

/*!
 * brief Requests CPC LMEM module's memory low power level change in software mode.
 *
 * note If request memory low power level change, must wait the MLPL transition complete.
 *
 * param base CPC LMEM module base address.
 */
void PGMC_CPC_LMEM_TriggerMLPLSoftwareChange(PGMC_CPC_Type *base)
{
    base->CPC_LMEM_CM_CTRL |= PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_SOFT_MASK;

    /* If request software change, check the MLPL transition status. */
    while (0UL != ((base->CPC_LMEM_CM_CTRL) & PGMC_CPC_CPC_LMEM_CM_CTRL_MLPL_SOFT_MASK))
    {
    }
}

/*!
 * brief Sets the behaviour of each signal(Such as Sleep signal) in MIF.
 *
 * note To control the memory low power operation, this function must be invoked after selecting the
 *       memory low power level.
 *       Use case:
 *        code
 *              PGMC_BPC_ControlPowerDomainByCpuPowerMode(PGMC_BPC0_BASE, kPGMC_WaitMode, kPGMC_CM7Core,
 *                  kPGMC_MLPLSleep, false);
 *              PGMC_MIF_SetSignalBehaviour(PGMC_BPC0_MIF_BASE, kPGMC_MLPLSleep, kPGMC_AssertSleepSignal);
 *        endcode
 *
 * param base PGMC MIF peripheral base address.
 * param memoryLevel The selected memory low power level. For details please refer to @ref
 * pgmc_memory_low_power_level_t.
 * param mask. The mask of MIF signal behaviour. Should be the OR'ed value of @ref _pgmc_mif_signal_behaviour
 */
void PGMC_MIF_SetSignalBehaviour(PGMC_MIF_Type *base, pgmc_memory_low_power_level_t memoryLevel, uint32_t mask)
{
    uint8_t signalIndex           = 0U;
    uint32_t temp32               = 0U;
    PGMC_MIF_SIGNAL_Type *MIF_SIG = (PGMC_MIF_SIGNAL_Type *)(uint32_t)(&(base->MIF_MLPL_SLEEP));

    for (signalIndex = 0U; signalIndex < 11U; signalIndex++)
    {
        temp32 = MIF_SIG[signalIndex].SIGNAL;
        temp32 &= ~(1UL << (uint32_t)memoryLevel);
        temp32 |= ((uint32_t)(mask & (1UL << signalIndex)) << (uint32_t)memoryLevel);
        MIF_SIG[signalIndex].SIGNAL = temp32;
    }
}

/*!
 * brief Makes the PMIC module controlled by the target CPU power mode(Such as Wait mode).
 *
 * param base PMIC module base address.
 * param mode Target CPU power mode.
 */
void PGMC_PPC_ControlByCpuPowerMode(PGMC_PPC_Type *base, pgmc_cpu_mode_t mode)
{
    base->PPC_MODE = PGMC_PPC_PPC_MODE_CTRL_MODE(kPGMC_ControlledByCpuPowerMode);
    switch (mode)
    {
        case kPGMC_RunMode:
            break;
        case kPGMC_WaitMode:
            base->PPC_STBY_CM_CTRL |= PGMC_PPC_PPC_STBY_CM_CTRL_STBY_ON_AT_WAIT_MASK;
            break;
        case kPGMC_StopMode:
            base->PPC_STBY_CM_CTRL |= PGMC_PPC_PPC_STBY_CM_CTRL_STBY_ON_AT_STOP_MASK;
            break;
        case kPGMC_SuspendMode:
            base->PPC_STBY_CM_CTRL |= PGMC_PPC_PPC_STBY_CM_CTRL_STBY_ON_AT_SUSPEND_MASK;
            break;
        default:
            assert(false);
            break;
    }
}

/*!
 * brief Makes the PMIC module controlled by the target set points.
 *
 * This function makes the module controlled by specific set point, It also supports PMIC standby on.
 *
 * note When setting more than one set point, use "|" between the map values in @ref _pgmc_setpoint_map.
 *
 * param base PMIC module base address.
 * param setPointMap Should be the OR'ed value of @ref _pgmc_setpoint_map.
 * param enableStandby true: PMIC standby on when system enters set point number and system is in standby mode.
 *                      false: PMIC standby on when system enters set point number
 */
void PGMC_PPC_ControlBySetPointMode(PGMC_PPC_Type *base, uint32_t setPointMap, bool enableStandby)
{
    setPointMap &= 0xFFFFU;

    base->PPC_MODE = PGMC_PPC_PPC_MODE_CTRL_MODE(kPGMC_ControlledBySetPoint);

    if (enableStandby)
    {
        base->PPC_STBY_SP_CTRL = (setPointMap << PGMC_PPC_PPC_STBY_SP_CTRL_STBY_ON_AT_SP_SLEEP_SHIFT);
    }
    else
    {
        base->PPC_STBY_SP_CTRL = setPointMap;
    }
}
