/*
 * SPDX-FileCopyrightText: Copyright (c) 2020-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "export_nvswitch.h"

#include "common_nvswitch.h"
#include "ls10/ls10.h"
#include "ls10/therm_ls10.h"
#include "error_nvswitch.h"
#include "soe/soeiftherm.h"

#include "flcn/flcnable_nvswitch.h"
#include "flcn/flcn_nvswitch.h"
#include "rmflcncmdif_nvswitch.h"
#include "soe/soeifcmn.h"

#include "nvswitch/ls10/dev_therm.h"

//
// Thermal functions
//

//
// Initialize thermal offsets for External Tdiode.
//

NvlStatus
nvswitch_init_thermal_ls10
(
    nvswitch_device *device
)
{
    ls10_device *chip_device = NVSWITCH_GET_CHIP_DEVICE_LS10(device);

    // Mark everything invalid
    chip_device->tdiode.method = NVSWITCH_THERM_METHOD_UNKNOWN;

    return NVL_SUCCESS;
}

static void
_nvswitch_read_max_tsense_temperature_ls10
(
    nvswitch_device *device,
    NVSWITCH_CTRL_GET_TEMPERATURE_PARAMS *info,
    NvU32 channel
)
{
    NvU32  offset;
    NvU32  temperature;

    temperature = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_MAXIMUM_TEMPERATURE);
    temperature = DRF_VAL(_THERM_TSENSE, _MAXIMUM_TEMPERATURE, _MAXIMUM_TEMPERATURE, temperature);
    temperature = NV_TSENSE_FXP_9_5_TO_24_8(temperature);

    if (channel == NVSWITCH_THERM_CHANNEL_LR10_TSENSE_MAX)
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_0_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_0_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);

        // Temperature of the sensor reported equals calculation of the max temperature reported
        // from the TSENSE HUB plus the temperature offset programmed by SW. This offset needs to
        // be substracted to get the actual temperature of the sensor.
        temperature -= NV_TSENSE_FXP_9_5_TO_24_8(offset);
    }

    info->temperature[channel] = temperature;
    info->status[channel] = NVL_SUCCESS;
}

static void
_nvswitch_read_external_tdiode_temperature_ls10
(
    nvswitch_device *device,
    NVSWITCH_CTRL_GET_TEMPERATURE_PARAMS *info,
    NvU32 channel
)
{
}

//
// nvswitch_therm_read_temperature
//
// Temperature and voltage are only available on SKUs which have thermal and
// voltage sensors.
//

NvlStatus
nvswitch_ctrl_therm_read_temperature_ls10
(
    nvswitch_device *device,
    NVSWITCH_CTRL_GET_TEMPERATURE_PARAMS *info
)
{
    NvU32 channel;
    NvU32 val;
    NvU32 offset;

    if (!info->channelMask)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: No channel given in the input.\n",
            __FUNCTION__);

        return -NVL_BAD_ARGS;
    }

    nvswitch_os_memset(info->temperature, 0x0, sizeof(info->temperature));

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_MAX;
    if (info->channelMask & NVBIT(channel))
    {
        _nvswitch_read_max_tsense_temperature_ls10(device, info, channel);
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_OFFSET_MAX;
    if (info->channelMask & NVBIT(channel))
    {
        _nvswitch_read_max_tsense_temperature_ls10(device, info, channel);        
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TDIODE;
    if (info->channelMask & NVBIT(channel))
    {
        _nvswitch_read_external_tdiode_temperature_ls10(device, info, channel);
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TDIODE_OFFSET;
    if (info->channelMask & NVBIT(channel))
    {
        _nvswitch_read_external_tdiode_temperature_ls10(device, info, channel);
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_0;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_0_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_0_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_0);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_0, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_1;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_1_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_1_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_1);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_1, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_2;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_2_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_2_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_2);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_2, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_3;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_3_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_3_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_3);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_3, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_4;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_4_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_4_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_4);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_4, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_5;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_5_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_5_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_5);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_5, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_6;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_6_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_6_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_6);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_6, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_7;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_7_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_7_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_7);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_7, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    channel = NVSWITCH_THERM_CHANNEL_LS10_TSENSE_8;
    if (info->channelMask & NVBIT(channel))
    {
        offset = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_8_TEMPERATURE_MODIFICATIONS);
        offset = DRF_VAL(_THERM_TSENSE, _U2_A_0_BJT_8_TEMPERATURE_MODIFICATIONS, _TEMPERATURE_OFFSET, offset);
        offset = NV_TSENSE_FXP_9_5_TO_24_8(offset);

        val = NVSWITCH_REG_RD32(device, _THERM, _TSENSE_U2_A_0_BJT_8);
        val = DRF_VAL(_THERM, _TSENSE_U2_A_0_BJT_8, _TEMPERATURE, val);
        val = NV_TSENSE_FXP_9_5_TO_24_8(val);
        val -= offset;

        info->temperature[channel] = val;
        info->channelMask &= ~NVBIT(channel);
    }

    if (info->channelMask)
    {
        NVSWITCH_PRINT(device, ERROR,
            "%s: ChannelMask %x absent on LS10.\n",
            __FUNCTION__, info->channelMask);

        return -NVL_BAD_ARGS;
    }

    return NVL_SUCCESS;
}

NvlStatus
nvswitch_ctrl_therm_get_temperature_limit_ls10
(
    nvswitch_device *device,
    NVSWITCH_CTRL_GET_TEMPERATURE_LIMIT_PARAMS *info
)
{
    NvU32 threshold;
    NvU32 temperature;

    threshold = nvswitch_reg_read_32(device, NV_THERM_TSENSE_THRESHOLD_TEMPERATURES);

    switch (info->thermalEventId)
    {
        case NVSWITCH_CTRL_THERMAL_EVENT_ID_WARN:
        {
            // Get Slowdown temperature
            temperature = DRF_VAL(_THERM_TSENSE, _THRESHOLD_TEMPERATURES,
                                  _WARNING_TEMPERATURE, threshold);
            break;
        }
        case NVSWITCH_CTRL_THERMAL_EVENT_ID_OVERT:
        {
            // Get Shutdown temperature
            temperature = DRF_VAL(_THERM_TSENSE, _THRESHOLD_TEMPERATURES,
                                  _OVERTEMP_TEMPERATURE, threshold);
            break;
        }
        default:
        {
            NVSWITCH_PRINT(device, ERROR, "Invalid Thermal Event Id: 0x%x\n", info->thermalEventId);
            return -NVL_BAD_ARGS;
        }
    }
    
    info->temperatureLimit = NV_TSENSE_FXP_9_5_TO_24_8(temperature);

    return NVL_SUCCESS;
}

// Background task to monitor thermal warn and adjust link mode
void
nvswitch_monitor_thermal_alert_ls10
(
    nvswitch_device *device
)
{
    return;
}

/*
 * @brief Callback function to recieve thermal messages from SOE.
 */
void
nvswitch_therm_soe_callback_ls10
(
    nvswitch_device *device,
    RM_FLCN_MSG *pGenMsg,
    void *pParams,
    NvU32 seqDesc,
    NV_STATUS status
)
{
    RM_SOE_THERM_MSG_SLOWDOWN_STATUS slowdown_status;
    RM_SOE_THERM_MSG_SHUTDOWN_STATUS shutdown_status;
    RM_FLCN_MSG_SOE *pMsg = (RM_FLCN_MSG_SOE *)pGenMsg;
    NvU32 temperature;
    NvU32 threshold;

    switch (pMsg->msg.soeTherm.msgType)
    {
        case RM_SOE_THERM_MSG_ID_SLOWDOWN_STATUS:
        {
            slowdown_status = pMsg->msg.soeTherm.slowdown;
            if (slowdown_status.bSlowdown)
            {
                if (slowdown_status.source.bTsense) // TSENSE_THERM_ALERT
                {
                    temperature = RM_SOE_NV_TEMP_TO_CELSIUS_TRUNCED(slowdown_status.maxTemperature);
                    threshold   = RM_SOE_NV_TEMP_TO_CELSIUS_TRUNCED(slowdown_status.warnThreshold);

                    NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_EVENT_START,
                        "NVSWITCH Temperature %dC | TSENSE WARN Threshold %dC\n",
                        temperature, threshold);

                    NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_EVENT_START,
                        "Thermal Slowdown Engaged | Temp higher than WARN Threshold\n");
                }

                NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_EVENT_START,
                    "Thermal Slowdown Engaged | Links Thermal Mode %s\n", (slowdown_status.bLinksL1Status ? "ON" : "OFF"));

                if (slowdown_status.source.bPmgr) // PMGR_THERM_ALERT
                {
                    NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_EVENT_START,
                        "Thermal Slowdown Engaged | PMGR WARN Threshold reached\n");
                }
            }
            else // REVERT_SLOWDOWN
            {
                temperature = RM_SOE_NV_TEMP_TO_CELSIUS_TRUNCED(slowdown_status.maxTemperature);
                threshold   = RM_SOE_NV_TEMP_TO_CELSIUS_TRUNCED(slowdown_status.warnThreshold);

                NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_EVENT_END,
                    "NVSWITCH Temperature %dC | TSENSE WARN Threshold %dC\n",
                    temperature, threshold);

                NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_EVENT_END,
                    "Thermal Slowdown Disengaged | Links Thermal Mode %s\n", (slowdown_status.bLinksL1Status ? "ON" : "OFF"));

                NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_EVENT_END,
                    "Thermal slowdown Disengaged\n");
            }
            break;
        }

        case RM_SOE_THERM_MSG_ID_SHUTDOWN_STATUS:
        {
            shutdown_status = pMsg->msg.soeTherm.shutdown;
            if (shutdown_status.source.bTsense) // TSENSE_THERM_SHUTDOWN
            {
                temperature = RM_SOE_NV_TEMP_TO_CELSIUS_TRUNCED(shutdown_status.maxTemperature);
                threshold   = RM_SOE_NV_TEMP_TO_CELSIUS_TRUNCED(shutdown_status.overtThreshold);

                NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_SHUTDOWN,
                    "NVSWITCH Temperature %dC | OVERT Threshold %dC\n",
                    temperature, threshold);

                NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_SHUTDOWN,
                    "TSENSE OVERT Threshold reached. Shutting Down\n");
            }

            if (shutdown_status.source.bPmgr) // PMGR_THERM_SHUTDOWN
            {
                NVSWITCH_PRINT_SXID(device, NVSWITCH_ERR_HW_HOST_THERMAL_EVENT_START,
                    "PMGR OVERT Threshold reached. Shutting Down\n");
            }
            break;
        }
        default:
        {
            NVSWITCH_PRINT(device, ERROR, "%s Unknown message Id\n", __FUNCTION__);
            NVSWITCH_ASSERT(0);
        }
    }
}

//
// nvswitch_therm_read_voltage
//
// Temperature and voltage are only available on SKUs which have thermal and
// voltage sensors.
//
NvlStatus
nvswitch_ctrl_therm_read_voltage_ls10
(
    nvswitch_device *device,
    NVSWITCH_CTRL_GET_VOLTAGE_PARAMS *pParams
)
{
    FLCN                *pFlcn;
    NvU32               cmdSeqDesc;
    NV_STATUS           status;
    NvU8                flcnStatus;
    RM_FLCN_CMD_SOE     cmd;
    RM_FLCN_MSG_SOE     msg;
    RM_SOE_CORE_CMD_GET_VOLTAGE *pGetVoltageCmd;
    NVSWITCH_TIMEOUT    timeout;

    if (!nvswitch_is_soe_supported(device))
    {
        return -NVL_ERR_NOT_SUPPORTED;
    }

    if (pParams == NULL)
    {
        return -NVL_BAD_ARGS;
    }

    pFlcn = device->pSoe->pFlcn;

    nvswitch_os_memset(pParams, 0, sizeof(NVSWITCH_CTRL_GET_VOLTAGE_PARAMS));
    nvswitch_os_memset(&cmd, 0, sizeof(RM_FLCN_CMD_SOE));
    nvswitch_os_memset(&msg, 0, sizeof(RM_FLCN_MSG_SOE));

    cmd.hdr.unitId = RM_SOE_UNIT_CORE;
    cmd.hdr.size   = RM_SOE_CMD_SIZE(CORE, GET_VOLTAGE);

    msg.hdr.unitId = RM_SOE_UNIT_CORE;
    msg.hdr.size   = RM_SOE_MSG_SIZE(CORE, GET_VOLTAGE);

    pGetVoltageCmd = &cmd.cmd.core.getVoltage;
    pGetVoltageCmd->cmdType = RM_SOE_CORE_CMD_GET_VOLTAGE_VALUES;

    cmdSeqDesc = 0;

    nvswitch_timeout_create(NVSWITCH_INTERVAL_1SEC_IN_NS * 5, &timeout);
    status = flcnQueueCmdPostBlocking(device, pFlcn,
                                      (PRM_FLCN_CMD)&cmd,
                                      (PRM_FLCN_MSG)&msg,   // pMsg
                                      NULL, // pPayload
                                      SOE_RM_CMDQ_LOG_ID,
                                      &cmdSeqDesc,
                                      &timeout);
    if (status != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: Failed to read VRs 0x%x\n", 
                       __FUNCTION__, status);
        return -NVL_ERR_INVALID_STATE;
    }

    flcnStatus = msg.msg.core.getVoltage.flcnStatus;
    if (flcnStatus != FLCN_OK)
    {
        if (flcnStatus == FLCN_ERR_MORE_PROCESSING_REQUIRED)
        {
            return -NVL_MORE_PROCESSING_REQUIRED;
        }
        else
        {
            return -NVL_ERR_GENERIC;
        }
    }

    pParams->vdd_mv = msg.msg.core.getVoltage.vdd_mv;
    pParams->dvdd_mv = msg.msg.core.getVoltage.dvdd_mv;
    pParams->hvdd_mv = msg.msg.core.getVoltage.hvdd_mv;

    return NVL_SUCCESS;
}

//
// nvswitch_therm_read_power
//
// Power is only available on SKUs which have thermal and
// voltage sensors.
//
NvlStatus
nvswitch_ctrl_therm_read_power_ls10
(
    nvswitch_device *device,
    NVSWITCH_GET_POWER_PARAMS *pParams
)
{
    FLCN                *pFlcn;
    NvU32               cmdSeqDesc;
    NV_STATUS           status;
    NvU8                flcnStatus;
    RM_FLCN_CMD_SOE     cmd;
    RM_FLCN_MSG_SOE     msg;
    RM_SOE_CORE_CMD_GET_POWER *pGetPowerCmd;
    NVSWITCH_TIMEOUT    timeout;

    if (!nvswitch_is_soe_supported(device))
    {
        return -NVL_ERR_NOT_SUPPORTED;
    }

    if (pParams == NULL)
    {
        return -NVL_BAD_ARGS;
    }

    pFlcn = device->pSoe->pFlcn;

    nvswitch_os_memset(pParams, 0, sizeof(NVSWITCH_GET_POWER_PARAMS));
    nvswitch_os_memset(&cmd, 0, sizeof(RM_FLCN_CMD_SOE));
    nvswitch_os_memset(&msg, 0, sizeof(RM_FLCN_MSG_SOE));

    cmd.hdr.unitId = RM_SOE_UNIT_CORE;
    cmd.hdr.size   = RM_SOE_CMD_SIZE(CORE, GET_POWER);

    msg.hdr.unitId = RM_SOE_UNIT_CORE;
    msg.hdr.size   = RM_SOE_MSG_SIZE(CORE, GET_POWER);

    pGetPowerCmd = &cmd.cmd.core.getPower;
    pGetPowerCmd->cmdType = RM_SOE_CORE_CMD_GET_POWER_VALUES;

    cmdSeqDesc = 0;

    nvswitch_timeout_create(NVSWITCH_INTERVAL_1SEC_IN_NS * 5, &timeout);
    status = flcnQueueCmdPostBlocking(device, pFlcn,
                                      (PRM_FLCN_CMD)&cmd,
                                      (PRM_FLCN_MSG)&msg,   // pMsg
                                      NULL, // pPayload
                                      SOE_RM_CMDQ_LOG_ID,
                                      &cmdSeqDesc,
                                      &timeout);
    if (status != NV_OK)
    {
        NVSWITCH_PRINT(device, ERROR, "%s: Failed to read power 0x%x\n", 
                       __FUNCTION__, status);
        return -NVL_ERR_INVALID_STATE;
    }

    flcnStatus = msg.msg.core.getPower.flcnStatus;
    if (flcnStatus != FLCN_OK)
    {
        if (flcnStatus == FLCN_ERR_MORE_PROCESSING_REQUIRED)
        {
            return -NVL_MORE_PROCESSING_REQUIRED;
        }
        else
        {
            return -NVL_ERR_GENERIC;
        }
    }

    pParams->vdd_w = msg.msg.core.getPower.vdd_w;
    pParams->dvdd_w = msg.msg.core.getPower.dvdd_w;
    pParams->hvdd_w = msg.msg.core.getPower.hvdd_w;

    return NVL_SUCCESS;
}