// SPDX-License-Identifier: MPL-2.0
//
// Do NOT modify or remove this copyright and license
//
// Copyright (c) 2012-2026 Seagate Technology LLC and/or its Affiliates, All Rights Reserved
//
// This software is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// ******************************************************************************************
//
// \file power_control.c
// \brief This file defines the functions for power related changes to drives.

#include "bit_manip.h"
#include "code_attributes.h"
#include "common_types.h"
#include "error_translation.h"
#include "io_utils.h"
#include "math_utils.h"
#include "memory_safety.h"
#include "precision_timer.h"
#include "prng.h"
#include "string_utils.h"
#include "type_conversion.h"

#include "cmds.h"
#include "logs.h"
#include "operations.h" //for reset to defaults bit check
#include "operations_Common.h"
#include "power_control.h"

// There is no specific way to enable or disable this on SCSI, so this simulates the behavior according to what we see
// with ATA
static eReturnValues scsi_Enable_Disable_EPC_Feature(const tDevice* device, eEPCFeatureSet lba_field, bool saveChanges)
{
    eReturnValues ret = UNKNOWN;
    // setup the structure and send these changes to the drive.
    powerConditionTimers powerTimers;
    safe_memset(&powerTimers, sizeof(powerConditionTimers), 0, sizeof(powerConditionTimers));

    if (lba_field == ENABLE_EPC)
    {
        // read the default mode page and write it back to the drive to set the default values from the drive...should
        // be the same as enabling
        ret = scsi_Set_Power_Conditions(device, true, &powerTimers, saveChanges);
    }
    else if (lba_field == DISABLE_EPC)
    {
        // read the current settings, disable any timers that are enabled and set the timer values to zero...should be
        // the same as disabling.
        powerTimers.idle_a.powerConditionValid    = true;
        powerTimers.idle_a.enable                 = false;
        powerTimers.idle_a.enableValid            = true;
        powerTimers.idle_b.powerConditionValid    = true;
        powerTimers.idle_b.enable                 = false;
        powerTimers.idle_b.enableValid            = true;
        powerTimers.idle_c.powerConditionValid    = true;
        powerTimers.idle_c.enable                 = false;
        powerTimers.idle_c.enableValid            = true;
        powerTimers.standby_y.powerConditionValid = true;
        powerTimers.standby_y.enable              = false;
        powerTimers.standby_y.enableValid         = true;
        powerTimers.standby_z.powerConditionValid = true;
        powerTimers.standby_z.enable              = false;
        powerTimers.standby_z.enableValid         = true;
        ret                                       = scsi_Set_Power_Conditions(device, false, &powerTimers, saveChanges);
    }
    else
    {
        ret = BAD_PARAMETER;
    }

    return ret;
}

//-----------------------------------------------------------------------------
//
//  enable_Disable_EPC_Feature (const tDevice *device, eEPCFeatureSet lba_field))
//
//! \brief   Enable the EPC Feature or Disable it [SATA Only)
//
//  Entry:
//!   \param[in]  device file descriptor
//!   \param[in]  lba_field what is the LBA Field should be set to.
//  Exit:
//!   \return SUCCESS = good, !SUCCESS something went wrong see error codes
//
//-----------------------------------------------------------------------------
eReturnValues enable_Disable_EPC_Feature(const tDevice* device, eEPCFeatureSet lba_field)
{
    eReturnValues ret = UNKNOWN;

    if (lba_field == ENABLE_EPC_NOT_SET)
    {
        ret = BAD_PARAMETER;
        return ret;
    }
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_SF_EPC(device, lba_field == ENABLE_EPC ? ATA_SF_ENABLE : ATA_SF_DISABLE);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        ret = scsi_Enable_Disable_EPC_Feature(device, lba_field, true);
    }
    else
    {
        ret = NOT_SUPPORTED;
    }
    return ret;
}

eReturnValues print_Current_Power_Mode(const tDevice* device)
{
    eReturnValues ret = UNKNOWN;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        uint8_t powerMode = UINT8_C(0);
        // first check if EPC feature is supported and/or enabled
        uint8_t  epcFeature   = UINT8_C(0); // 0 - disabled, 1 - supported, 2 - enabled.
        uint8_t* identifyData = C_CAST(
            uint8_t*, safe_calloc_aligned(LEGACY_DRIVE_SEC_SIZE, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (identifyData == M_NULLPTR)
        {
            perror("Calloc Failure!\n");
            return MEMORY_FAILURE;
        }

        if (SUCCESS == ata_Identify(device, identifyData, LEGACY_DRIVE_SEC_SIZE))
        {
            // check word 119 bit 7 for EPC support
            uint16_t* identWordPTR = C_CAST(uint16_t*, identifyData);
            if ((identWordPTR[119] & BIT7) > 0)
            {
                epcFeature = 1;
            }
            // check word 120 bit 7 for EPC enabled
            if ((identWordPTR[120] & BIT7) > 0)
            {
                epcFeature = 2;
            }
        }
        else
        {
            print_str("Unable to detect if EPC feature status! Cannot continue!\n"); // this SHOULDN'T happen
            safe_free_aligned(&identifyData);
            return FAILURE;
        }
        safe_free_aligned(&identifyData);

        if (SUCCESS == ata_Check_Power_Mode(device, &powerMode))
        {
            print_str("Device is in the ");
            switch (powerMode)
            {
            case 0x00:
                if (epcFeature != 2)
                {
                    print_str("PM2: Standby state.\n");
                }
                else
                {
                    print_str("PM2: Standby state and device is in the Standby_z power condition\n");
                }
                break;
            case 0x01: // should only happen when EPC is enabled according to the spec...so not checking EPC feature
                print_str("PM2: Standby state and the device is in the Standby_y power condition\n");
                break;
            case 0x40: // NV cache
                print_str("PM0: Active state. NV Cache power is enabled and spindle is spun/spinning down\n");
                break;
            case 0x41: // NV cache
                print_str("PM0: Active state. NV Cache power is enabled and spindle is spun/spinning up\n");
                break;
            case 0x80:
                if (epcFeature == 0)
                {
                    print_str("PM1: Idle state\n");
                }
                else
                {
                    print_str("PM1: Idle state. EPC feature disabled");
                }
                break;
            case 0x81: // should only happen when EPC is enabled according to the spec...so not checking EPC feature
                print_str("PM1: Idle state and the device is in the Idle_a power condition\n");
                break;
            case 0x82: // should only happen when EPC is enabled according to the spec...so not checking EPC feature
                print_str("PM1: Idle state and the device is in the Idle_b power condition\n");
                break;
            case 0x83: // should only happen when EPC is enabled according to the spec...so not checking EPC feature
                print_str("PM1: Idle state and the device is in the Idle_c power condition\n");
                break;
            case 0xFF:
                print_str("PM0: Active state or PM1: Idle State\n");
                break;
            default:
                print_str("Unknown/Reserved Power State\n");
                break;
            }
            ret = SUCCESS;
        }
        else
        {
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                print_str("Unable to retrive current power mode!\n");
            }
            ret = FAILURE;
        }
    }
    else if (device->drive_info.drive_type == NVME_DRIVE)
    {
        uint32_t powerMode = UINT32_C(0);
        ret                = get_Power_State(device, &powerMode, CURRENT_VALUE);
        if (ret == SUCCESS)
        {
            printf("Device is in Power State %" PRIu32 "\n", powerMode);
        }
        else
        {
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                print_str("Unable to retrive current power state!\n");
            }
        }
    }
    else
    {
        /*
        NOTE: Removed the code which was checking to see if the power mode is supported
              mainly because it was changing the power state of the drive. -MA
        */
        uint8_t* senseData = M_REINTERPRET_CAST(
            uint8_t*, safe_calloc_aligned(SPC3_SENSE_LEN, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (senseData == M_NULLPTR)
        {
            perror("Calloc Failure!\n");
            return MEMORY_FAILURE;
        }
        if (SUCCESS == scsi_Request_Sense_Cmd(device, false, senseData, SPC3_SENSE_LEN))
        {
            bool issuetur = false;
            // requested fixed format sensedata, so parse it. If we don't find what we are looking for post the "unable
            // to retrieve current power mode" message
            if ((senseData[0] & 0x7F) == SCSI_SENSE_CUR_INFO_FIXED ||
                (senseData[0] & 0x7F) == SCSI_SENSE_DEFER_ERR_FIXED)
            {
                uint8_t acs  = senseData[12];
                uint8_t acsq = senseData[13];
                ret          = SUCCESS;
                print_str("Device is in the ");
                switch (acs)
                {
                case 0x5E:
                    switch (acsq)
                    {
                    case 0x00:
                        print_str("Low Power state\n");
                        break;
                    case 0x01:
                        print_str("Idle state activated by timer\n");
                        break;
                    case 0x03:
                        print_str("Idle state activated by host command\n");
                        break;
                    case 0x02:
                        print_str("Standby_Z state activated by timer\n");
                        break;
                    case 0x04:
                        print_str("Standby_Z state activated by host command\n");
                        break;
                    case 0x05:
                        print_str("Idle_B state activated by timer\n");
                        break;
                    case 0x06:
                        print_str("Idle_B state activated by host command\n");
                        break;
                    case 0x07:
                        print_str("Idle_C state activated by timer\n");
                        break;
                    case 0x08:
                        print_str("Idle_C state activated by host command\n");
                        break;
                    case 0x09:
                        print_str("Standby_Y state activated by timer\n");
                        break;
                    case 0x0A:
                        print_str("Standby_Y state activated by host command\n");
                        break;
                    default:
                        issuetur = true;
                        break;
                    }
                    break;
                default:
                    issuetur = true;
                    break;
                }
            }
            else
            {
                issuetur = true;
            }
            if (issuetur == true)
            {
                scsiStatus returnedStatus;
                safe_memset(&returnedStatus, sizeof(scsiStatus), 0, sizeof(scsiStatus));
                ret = scsi_Test_Unit_Ready(device, &returnedStatus);
                if ((ret == SUCCESS) && (returnedStatus.senseKey == SENSE_KEY_NO_ERROR))
                {
                    // assume active state
                    print_str("Device is in active state or an unknown power state.\n");
                }
                else if (returnedStatus.senseKey == SENSE_KEY_NOT_READY)
                {
                    // check asc and ascq if spinup command is required
                    if (returnedStatus.asc == 0x04 && returnedStatus.ascq == 0x02)
                    {
                        print_str("Standby state\n"); // activated by host command???
                    }
                    else
                    {
                        print_str("Unknown power state. Unit reports: ");
                        show_Test_Unit_Ready_Status(device);
                    }
                }
                else
                {
                    print_str("Unknown power state. Unit reports: ");
                    show_Test_Unit_Ready_Status(device);
                    ret = FAILURE;
                }
            }
        }
        safe_free_aligned(&senseData);
    }
    return ret;
}

eReturnValues transition_Power_State(const tDevice* device, ePowerConditionID newState)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        switch (newState)
        {
        case PWR_CND_STANDBY_Z:
            ret = ata_SF_EPC_Go_To_Power_Condition(device, PWR_CND_STANDBY_Z, false, false);
            break;
        case PWR_CND_STANDBY_Y:
            ret = ata_SF_EPC_Go_To_Power_Condition(device, PWR_CND_STANDBY_Y, false, false);
            break;
        case PWR_CND_IDLE_A:
            ret = ata_SF_EPC_Go_To_Power_Condition(device, PWR_CND_IDLE_A, false, false);
            break;
        case PWR_CND_IDLE_B:
            ret = ata_SF_EPC_Go_To_Power_Condition(device, PWR_CND_IDLE_B, false, false);
            break;
        case PWR_CND_IDLE_C:
            ret = ata_SF_EPC_Go_To_Power_Condition(device, PWR_CND_IDLE_C, false, false);
            break;
        case PWR_CND_ACTIVE:
            // No such thing in ATA. Attempt by sending read-verify to a few sectors on the disk randomly (Early SAT
            // translation recommended this behavior)
            seed_64(C_CAST(uint64_t, time(M_NULLPTR)));
            for (uint8_t counter = UINT8_C(0); counter < 5; ++counter)
            {
                uint64_t lba = UINT64_C(0);
                lba          = random_Range_64(0, device->drive_info.deviceMaxLba);
                ata_Read_Verify(device, lba, 1);
            }
            // TODO: zoned devices will require a different method. See SAT4 or later for details.
            // SAT-6 says:
            // EPC: set features for all supported power conditions: ID set to FFh, enable set to zero, save set to zero
            // to disable all current timers
            //      Then issue an idle immediate. If no errors: In active condition, else return an error.
            // non-EPC: send idle with feature 0, count 0, lba 0. If no error: In active state.
            ret = SUCCESS;
            break;
        case PWR_CND_IDLE: // send idle immediate
            ret = ata_Idle_Immediate(device, false);
            break;
        case PWR_CND_IDLE_UNLOAD: // send idle immediate - unload
            if ((is_ATA_Identify_Word_Valid_With_Bits_14_And_15(
                     le16_to_host(device->drive_info.IdentifyData.ata.Word084)) &&
                 le16_to_host(device->drive_info.IdentifyData.ata.Word084) & BIT13) ||
                (is_ATA_Identify_Word_Valid_With_Bits_14_And_15(
                     le16_to_host(device->drive_info.IdentifyData.ata.Word087)) &&
                 le16_to_host(device->drive_info.IdentifyData.ata.Word087) & BIT13))
            {
                ret = ata_Idle_Immediate(device, true);
            }
            else
            {
                ret = NOT_SUPPORTED;
            }
            break;
        case PWR_CND_STANDBY: // send standby immediate
            ret = ata_Standby_Immediate(device);
            break;
        case PWR_CND_SLEEP: // send sleep command
            ret = ata_Sleep(device);
            break;
        default:
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                print_str("Power State Transition is not supported on this device type at this time\n");
            }
            ret = NOT_SUPPORTED;
            break;
        }
    }
    else // if (device->drive_info.drive_type == SCSI_DRIVE) /*removed the if SCSI here to handle NVMe or other
         // translations*/
    {
        switch (newState)
        {
        case PWR_CND_ACTIVE:
            if (device->drive_info.scsiVersion >
                SCSI_VERSION_SCSI2) // checking for support after SCSI2. This isn't perfect, but should be ok for now.
            {
                ret = scsi_Start_Stop_Unit(device, false, 0, PC_ACTIVE, false, false, false);
            }
            else
            {
                // before you could specify a power condition, you used the "Start" bit as a way to move from standby to
                // active
                ret = scsi_Start_Stop_Unit(device, false, 0, PC_START_VALID, false, false, true);
            }
            break;
        case PWR_CND_STANDBY_Z:
            ret = scsi_Start_Stop_Unit(device, false, 0, PC_STANDBY, false, false, false);
            break;
        case PWR_CND_STANDBY_Y:
            ret = scsi_Start_Stop_Unit(device, false, 1, PC_STANDBY, false, false, false);
            break;
        case PWR_CND_IDLE_A:
            ret = scsi_Start_Stop_Unit(device, false, 0, PC_IDLE, false, false, false);
            break;
        case PWR_CND_IDLE_B:
            ret = scsi_Start_Stop_Unit(device, false, 1, PC_IDLE, false, false, false);
            break;
        case PWR_CND_IDLE_C:
            ret = scsi_Start_Stop_Unit(device, false, 2, PC_IDLE, false, false, false);
            break;
        case PWR_CND_IDLE: // send idle immediate
            ret = scsi_Start_Stop_Unit(device, false, 0, PC_IDLE, false, false, false);
            break;
        case PWR_CND_IDLE_UNLOAD: // send idle immediate - unload
            if (device->drive_info.scsiVersion > SCSI_VERSION_SPC_2)
            {
                ret = scsi_Start_Stop_Unit(device, false, 1, PC_IDLE, false, false, false);
            }
            break;
        case PWR_CND_STANDBY: // send standby immediate
            if (device->drive_info.scsiVersion >
                SCSI_VERSION_SCSI2) // checking for support after SCSI2. This isn't perfect, but should be ok for now.
            {
                ret = scsi_Start_Stop_Unit(device, false, 0, PC_STANDBY, false, false, false);
            }
            else
            {
                // before you could specify a power condition, you used the "Start" bit as a way to move from standby to
                // active
                ret = scsi_Start_Stop_Unit(device, false, 0, 0, false, false, false);
            }
            break;
        case PWR_CND_SLEEP: // send sleep command
            if (device->drive_info.scsiVersion >
                SCSI_VERSION_SCSI2) // checking for support after SCSI2. This isn't perfect, but should be ok for now.
            {
                ret = scsi_Start_Stop_Unit(device, false, 0, PC_SLEEP, false, false,
                                           false); // This is obsolete since SBC2...but we'll send it anyways
            }
            break;
        default:
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                print_str("Power State Transition is not supported on this device type at this time\n");
            }
            ret = NOT_SUPPORTED;
            break;
        }
    }
    return ret;
}

eReturnValues get_NVMe_Power_States(const tDevice* device, ptrNVMeSupportedPowerStates nvmps)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (device != M_NULLPTR && device->drive_info.drive_type == NVME_DRIVE && nvmps != M_NULLPTR)
    {
        ret = SUCCESS;
        // use cached NVMe identify ctrl data since this won't change.
        uint16_t driveMaxPowerStates = NVME_0_BASED(device->drive_info.IdentifyData.nvme.ctrl.npss);
        safe_memset(nvmps, sizeof(nvmeSupportedPowerStates), 0, sizeof(nvmeSupportedPowerStates));
        for (uint16_t powerIter = UINT16_C(0); powerIter < driveMaxPowerStates && powerIter < MAXIMUM_NVME_POWER_STATES;
             ++powerIter)
        {
            nvmps->powerState[powerIter].powerStateNumber = powerIter;
            // set max power if available
            if (le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].maxPower) > 0)
            {
                nvmps->powerState[powerIter].maxPowerValid = true;
                nvmps->powerState[powerIter].maxPowerMilliWatts =
                    le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].maxPower);
                if ((device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].flags & BIT0) == 0)
                {
                    // reported in centiwatts, so convert it
                    nvmps->powerState[powerIter].maxPowerMilliWatts *= 10;
                }
            }
            if (device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].flags & BIT1)
            {
                nvmps->powerState[powerIter].isNonOperationalPS = true;
            }
            // entry exit latency
            nvmps->powerState[powerIter].entryLatency =
                le32_to_host(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].entryLat);
            nvmps->powerState[powerIter].exitLatency =
                le32_to_host(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].exitLat);
            // r/w throughput and latency are all 5bit fields, so stripping off the top 3 bits when assigning in case
            // future revisions make changes.
            nvmps->powerState[powerIter].relativeReadThroughput =
                device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].readTPut & 0x1F;
            nvmps->powerState[powerIter].relativeReadLatency =
                device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].readLat & 0x1F;
            nvmps->powerState[powerIter].relativeWriteThroughput =
                device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].writeLput & 0x1F;
            nvmps->powerState[powerIter].relativeWriteLatency =
                device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].writeLat & 0x1F;
            // set idle power if available
            if (le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].idlePower) > 0)
            {
                nvmps->powerState[powerIter].idlePowerValid = true;
                nvmps->powerState[powerIter].idlePowerMilliWatts =
                    le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].idlePower);
                uint8_t scale =
                    get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].idleScale, 7, 6);
                if (scale == 2)
                {
                    // reported in centiwatts, so convert it
                    nvmps->powerState[powerIter].idlePowerMilliWatts *= 10;
                }
                else if (scale == 3 || scale == 0)
                {
                    // cannot handle these cases, so disable reporting idle power since we cannot report it correctly
                    nvmps->powerState[powerIter].idlePowerValid = false;
                }
            }
            // set active power if available
            if (le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].activePower) > 0)
            {
                nvmps->powerState[powerIter].activePowerValid = true;
                nvmps->powerState[powerIter].activePowerMilliWatts =
                    le16_to_host(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].activePower);
                nvmps->powerState[powerIter].activePowerWorkload =
                    get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].activeWorkScale, 2, 0);
                uint8_t scale =
                    get_bit_range_uint8(device->drive_info.IdentifyData.nvme.ctrl.psd[powerIter].activeWorkScale, 7, 6);
                if (scale == 2)
                {
                    // reported in centiwatts, so convert it
                    nvmps->powerState[powerIter].idlePowerMilliWatts *= 10;
                }
                else if (scale == 3 || scale == 0)
                {
                    // cannot handle these cases, so disable reporting idle power since we cannot report it correctly
                    nvmps->powerState[powerIter].activePowerValid = false;
                }
            }
            nvmps->numberOfPowerStates += 1;
        }
        // finish by reading which is the current power state that the device is operating in
        get_Power_State(device, &nvmps->activePowerState, CURRENT_VALUE);
    }

    return ret;
}

static uint8_t calculate_Relative_NVM_Latency_Or_Throughput(uint8_t value, uint16_t numberOfPowerStates)
{
    uint8_t relativeVal =
        C_CAST(uint8_t, 100.0 - (C_CAST(double, value) / C_CAST(double, numberOfPowerStates) * 100.0));

    return relativeVal;
}

// convert the entry/exit latencies into human readable strings
// This was basically copy-pasted from common_public.c in opensea-transport. Need to make this a function in
// opensea-common instead
#define NVM_POWER_ENT_EX_TIME_MAX_STR_LEN 10
static const char* convert_NVM_Latency_To_HR_Time_Str(uint64_t timeInNanoSeconds,
                                                      char     timeStr[NVM_POWER_ENT_EX_TIME_MAX_STR_LEN])
{
    const char* ctimestr    = &timeStr[0];
    double      printTime   = C_CAST(double, timeInNanoSeconds);
    uint8_t     unitCounter = UINT8_C(0);
    bool        breakLoop   = false;
    while (printTime > 1 && unitCounter <= 6)
    {
        switch (unitCounter)
        {
        case 6: // shouldn't get this far...
            break;
        case 5: // h to d
            if ((printTime / 24.0) < 1)
            {
                breakLoop = true;
            }
            break;
        case 4: // m to h
        case 3: // s to m
            if ((printTime / 60.0) < 1)
            {
                breakLoop = true;
            }
            break;
        case 0: // ns to us
        case 1: // us to ms
        case 2: // ms to s
        default:
            if ((printTime / 1000.0) < 1)
            {
                breakLoop = true;
            }
            break;
        }
        if (breakLoop)
        {
            break;
        }
        switch (unitCounter)
        {
        case 6: // shouldn't get this far...
            break;
        case 5: // h to d
            printTime /= 24.0;
            break;
        case 4: // m to h
        case 3: // s to m
            printTime /= 60.0;
            break;
        case 0: // ns to us
        case 1: // us to ms
        case 2: // ms to s
        default:
            printTime /= 1000.0;
            break;
        }
        if (unitCounter == 6)
        {
            break;
        }
        ++unitCounter;
    }
#define NVM_LAT_UNIT_STR_LEN 3
    DECLARE_ZERO_INIT_ARRAY(char, units, NVM_LAT_UNIT_STR_LEN);
    switch (unitCounter)
    {
    case 6: // we shouldn't get to a days value, but room for future large drives I guess...-TJE
        snprintf_err_handle(units, NVM_LAT_UNIT_STR_LEN, "d");
        break;
    case 5:
        snprintf_err_handle(units, NVM_LAT_UNIT_STR_LEN, "h");
        break;
    case 4:
        snprintf_err_handle(units, NVM_LAT_UNIT_STR_LEN, "m");
        break;
    case 3:
        snprintf_err_handle(units, NVM_LAT_UNIT_STR_LEN, "s");
        break;
    case 2:
        snprintf_err_handle(units, NVM_LAT_UNIT_STR_LEN, "ms");
        break;
    case 1:
        snprintf_err_handle(units, NVM_LAT_UNIT_STR_LEN, "us");
        break;
    case 0:
        snprintf_err_handle(units, NVM_LAT_UNIT_STR_LEN, "ns");
        break;
    default: // couldn't get a good conversion or something weird happened so show original nanoseconds.
        snprintf_err_handle(units, NVM_LAT_UNIT_STR_LEN, "ns");
        printTime = C_CAST(double, timeInNanoSeconds);
        break;
    }
    snprintf_err_handle(timeStr, NVM_POWER_ENT_EX_TIME_MAX_STR_LEN, "%0.02f %s", printTime, units);
    return ctimestr;
}

#define NVM_POWER_WATTS_MAX_STR_LEN 10
void print_NVM_Power_States(ptrNVMeSupportedPowerStates nvmps)
{

    if (nvmps != M_NULLPTR)
    {
        print_str("\nSupported NVMe Power States\n");
        // flags = non operational, current power state
        print_str("\t* = current power state\n");
        print_str("\t! = non-operational power state\n");
        print_str("\tNR = this value was not reported by the device\n");
        print_str("\n\tRead/write through put and latency meanings:\n");
        print_str("\t\tRRT = Relative Read Throughput\n");
        print_str("\t\tRRL = Relative Read Latency\n");
        print_str("\t\tRWT = Relative Write Throughput\n");
        print_str("\t\tRWL = Relative Write Latency\n");
        print_str("\t\tRead/Write throughput and latency values are scaled from 0 - 100%.\n");
        print_str("\t100% = max performance, 0% = minimum relative performance.\n");
        // flags | # | max power | idle power | active power | latencies and throughputs (can be N/A when not reported)
        print_str("\n   #  Max Power: Idle Power: Active Power: RRT: RRL: RWT: RWL: Entry Time: Exit Time:\n");
        print_str("-------------------------------------------------------------------------------------\n");
        // all values should be in watts. 1.00 watts, 0.25 watts, etc
        // should relative values be scaled to percentages? 100% = max performance. This may be easier for some to
        // understand than a random number
        for (uint16_t psIter = UINT16_C(0); psIter < nvmps->numberOfPowerStates && psIter < MAXIMUM_NVME_POWER_STATES;
             ++psIter)
        {
            char flags[3] = {' ', ' ', '\0'};
            DECLARE_ZERO_INIT_ARRAY(char, maxPowerWatts, NVM_POWER_WATTS_MAX_STR_LEN);
            DECLARE_ZERO_INIT_ARRAY(char, idlePowerWatts, NVM_POWER_WATTS_MAX_STR_LEN);
            DECLARE_ZERO_INIT_ARRAY(char, activePowerWatts, NVM_POWER_WATTS_MAX_STR_LEN);
            DECLARE_ZERO_INIT_ARRAY(char, entryTime, NVM_POWER_ENT_EX_TIME_MAX_STR_LEN);
            DECLARE_ZERO_INIT_ARRAY(char, exitTime, NVM_POWER_ENT_EX_TIME_MAX_STR_LEN);
            if (nvmps->activePowerState == nvmps->powerState[psIter].powerStateNumber)
            {
                // mark as active with a *
                flags[0] = '*';
            }
            if (nvmps->powerState[psIter].isNonOperationalPS)
            {
                // mark as non operational with a !
                flags[1] = '!';
            }
            if (nvmps->powerState[psIter].maxPowerValid)
            {
                snprintf_err_handle(maxPowerWatts, NVM_POWER_WATTS_MAX_STR_LEN, "%.4f W",
                                    (nvmps->powerState[psIter].maxPowerMilliWatts / 1000.0));
            }
            else
            {
                snprintf_err_handle(maxPowerWatts, NVM_POWER_WATTS_MAX_STR_LEN, "NR");
            }
            if (nvmps->powerState[psIter].idlePowerValid)
            {
                snprintf_err_handle(idlePowerWatts, NVM_POWER_WATTS_MAX_STR_LEN, "%.4f W",
                                    (nvmps->powerState[psIter].idlePowerMilliWatts / 1000.0));
            }
            else
            {
                snprintf_err_handle(idlePowerWatts, NVM_POWER_WATTS_MAX_STR_LEN, "NR");
            }
            if (nvmps->powerState[psIter].activePowerValid)
            {
                snprintf_err_handle(activePowerWatts, NVM_POWER_WATTS_MAX_STR_LEN, "%.4f W",
                                    (nvmps->powerState[psIter].activePowerMilliWatts / 1000.0));
            }
            else
            {
                snprintf_err_handle(activePowerWatts, NVM_POWER_WATTS_MAX_STR_LEN, "NR");
            }
            if (nvmps->powerState[psIter].entryLatency > 0)
            {
                convert_NVM_Latency_To_HR_Time_Str(nvmps->powerState[psIter].entryLatency * UINT64_C(1000), entryTime);
            }
            else
            {
                snprintf_err_handle(entryTime, NVM_POWER_ENT_EX_TIME_MAX_STR_LEN, "NR");
            }
            if (nvmps->powerState[psIter].exitLatency > 0)
            {
                convert_NVM_Latency_To_HR_Time_Str(nvmps->powerState[psIter].exitLatency * UINT64_C(1000), exitTime);
            }
            else
            {
                snprintf_err_handle(exitTime, NVM_POWER_ENT_EX_TIME_MAX_STR_LEN, "NR");
            }
            printf("%s%2" PRIu16 " %10s  %10s    %10s %4" PRIu8 " %4" PRIu8 " %4" PRIu8 " %4" PRIu8 "  %10s %10s\n",
                   flags, nvmps->powerState[psIter].powerStateNumber, maxPowerWatts, idlePowerWatts, activePowerWatts,
                   calculate_Relative_NVM_Latency_Or_Throughput(nvmps->powerState[psIter].relativeReadThroughput,
                                                                nvmps->numberOfPowerStates - 1),
                   calculate_Relative_NVM_Latency_Or_Throughput(nvmps->powerState[psIter].relativeReadLatency,
                                                                nvmps->numberOfPowerStates - 1),
                   calculate_Relative_NVM_Latency_Or_Throughput(nvmps->powerState[psIter].relativeWriteThroughput,
                                                                nvmps->numberOfPowerStates - 1),
                   calculate_Relative_NVM_Latency_Or_Throughput(nvmps->powerState[psIter].relativeWriteLatency,
                                                                nvmps->numberOfPowerStates - 1),
                   entryTime, exitTime);
        }
    }
}

eReturnValues transition_NVM_Power_State(const tDevice* device, uint8_t newState)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == NVME_DRIVE)
    {
        nvmeFeaturesCmdOpt cmdOpts;
        safe_memset(&cmdOpts, sizeof(cmdOpts), 0, sizeof(cmdOpts));
        cmdOpts.featSetGetValue = newState;
        cmdOpts.fid             = NVME_FEAT_POWER_MGMT_;
        cmdOpts.sel             = NVME_CURRENT_FEAT_SEL;
        ret                     = nvme_Set_Features(device, &cmdOpts);
        if (ret != SUCCESS)
        {
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                print_str("Power State Transition Failed in NVMe Set Features command\n");
            }
        }
    }
    return ret;
}

static eReturnValues ata_Set_EPC_Power_Mode(const tDevice*            device,
                                            ePowerConditionID         powerCondition,
                                            ptrPowerConditionSettings powerConditionSettings,
                                            bool                      saveChanges)
{
    eReturnValues ret = SUCCESS;

    if (powerConditionSettings == M_NULLPTR || powerCondition == PWR_CND_ACTIVE)
    {
        return BAD_PARAMETER;
    }

    if (powerConditionSettings->powerConditionValid)
    {
        if (powerConditionSettings->restoreToDefault)
        {
            // this command is restoring the power conditions from the drive's default settings (bit6) and saving them
            // upon completion (bit4)...the other option is to return to the saved settings, but we aren't going to
            // support that with this option right now
            ret = ata_SF_EPC_Restore_Power_Condition_Settings(device, powerCondition, true, saveChanges);
        }
        else // we aren't restoring settings, so we need to set things up to save settings
        {
            if (powerConditionSettings->timerValid)
            {
                bool     minutes = false;
                uint16_t timerVal =
                    M_STATIC_CAST(uint16_t, powerConditionSettings->timerInHundredMillisecondIncrements);
                if (powerConditionSettings->timerInHundredMillisecondIncrements > UINT16_MAX)
                {
                    // need to convert to a number of minutes to send to the drive instead!
                    uint64_t convertedMinutes =
                        (C_CAST(uint64_t, powerConditionSettings->timerInHundredMillisecondIncrements) *
                         UINT64_C(100)) /
                        UINT64_C(60000);
                    // now, this value should be able to be sent...
                    timerVal = M_STATIC_CAST(uint16_t, convertedMinutes);
                    minutes  = true;
                }
                ret = ata_SF_EPC_Set_Power_Condition_Timer(
                    device, powerCondition, timerVal, minutes,
                    powerConditionSettings->enableValid && powerConditionSettings->enable ? true : false, saveChanges);
            }
            else // they didn't enter a timer value so this command will do the EXACT same thing...just decided to use a
                 // different feature to EPC
            {
                ret = ata_SF_EPC_Set_Power_Condition_State(
                    device, powerCondition,
                    powerConditionSettings->enableValid && powerConditionSettings->enable ? true : false, saveChanges);
            }
        }
    }
    return ret;
}

eReturnValues scsi_Set_Power_Conditions(const tDevice*          device,
                                        bool                    restoreAllToDefaults,
                                        ptrPowerConditionTimers powerConditions,
                                        bool                    saveChanges)
{
    // If restore all to defaults, then read the default mode page, then write it back.
    // Write all applicable changes back to the drive, checking each structure as it goes.
    eReturnValues ret                       = SUCCESS;
    uint16_t      powerConditionsPageLength = UINT16_C(0);
    uint8_t*      powerConditionsPage       = M_NULLPTR;
    if (restoreAllToDefaults)
    {
        if (scsi_MP_Reset_To_Defaults_Supported(device))
        {
            ret = scsi_Mode_Select_10(device, 0, true, saveChanges, true, M_NULLPTR,
                                      0); // RTD bit is set and supported by the drive which will reset the page to
                                          // defaults for us without a data transfer or multiple commands.
        }
        else
        {
            // read the default mode page, then send it to the drive with mode select.
            powerConditionsPageLength =
                MODE_PARAMETER_HEADER_10_LEN +
                MP_POWER_CONDITION_LEN; //*should* be maximum size we need assuming no block descriptor
            powerConditionsPage =
                M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(powerConditionsPageLength, sizeof(uint8_t),
                                                                 device->os_info.minimumAlignment));
            if (powerConditionsPage == M_NULLPTR)
            {
                return MEMORY_FAILURE;
            }
            if (SUCCESS == (ret = scsi_Mode_Sense_10(device, MP_POWER_CONDTION, powerConditionsPageLength, 0, true,
                                                     false, MPC_DEFAULT_VALUES, powerConditionsPage)))
            {
                // got the page, now send it to the drive with a mode select
                ret = scsi_Mode_Select_10(device, powerConditionsPageLength, true, saveChanges, false,
                                          powerConditionsPage, powerConditionsPageLength);
            }
            safe_free_aligned(&powerConditionsPage);
        }
    }
    else
    {

        if (powerConditions == M_NULLPTR)
        {
            return BAD_PARAMETER;
        }

        // Check if anything in the incoming list is requesting default values so we can allocate and read the defaults
        // for those conditions before sending to the drive.
        if ((powerConditions->idle_a.powerConditionValid && powerConditions->idle_a.restoreToDefault) ||
            (powerConditions->idle_b.powerConditionValid && powerConditions->idle_b.restoreToDefault) ||
            (powerConditions->idle_c.powerConditionValid && powerConditions->idle_c.restoreToDefault) ||
            (powerConditions->standby_y.powerConditionValid && powerConditions->standby_y.restoreToDefault) ||
            (powerConditions->standby_z.powerConditionValid && powerConditions->standby_z.restoreToDefault) ||
            (powerConditions->powerModeBackgroundValid && powerConditions->powerModeBackgroundResetDefault) ||
            (powerConditions->checkConditionFlags.ccfIdleValid &&
             powerConditions->checkConditionFlags.ccfIdleResetDefault) ||
            (powerConditions->checkConditionFlags.ccfStandbyValid &&
             powerConditions->checkConditionFlags.ccfStandbyResetDefault) ||
            (powerConditions->checkConditionFlags.ccfStopValid &&
             powerConditions->checkConditionFlags.ccfStopResetDefault))
        {
            // Read the default mode page, then save whichever things need defaults into the proper structure
            powerConditionsPageLength =
                MODE_PARAMETER_HEADER_10_LEN +
                MP_POWER_CONDITION_LEN; //*should* be maximum size we need assuming no block descriptor
            powerConditionsPage =
                M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(powerConditionsPageLength, sizeof(uint8_t),
                                                                 device->os_info.minimumAlignment));
            if (powerConditionsPage == M_NULLPTR)
            {
                return MEMORY_FAILURE;
            }
            if (SUCCESS == (ret = scsi_Mode_Sense_10(device, MP_POWER_CONDTION, powerConditionsPageLength, 0, true,
                                                     false, MPC_DEFAULT_VALUES, powerConditionsPage)))
            {
                // uint16_t modeDataLength = M_BytesTo2ByteValue(powerConditionsPage[0], powerConditionsPage[1]);
                uint16_t blockDescriptorLength =
                    M_BytesTo2ByteValue(powerConditionsPage[MODE_HEADER_10_BLK_DESC_OFFSET],
                                        powerConditionsPage[MODE_HEADER_10_BLK_DESC_OFFSET + 1]);
                uint32_t mpStartOffset = MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength;
                // read the power conditions values that were requested
                if (powerConditions->idle_a.powerConditionValid && powerConditions->idle_a.restoreToDefault)
                {
                    powerConditions->idle_a.enableValid = true;
                    powerConditions->idle_a.enable      = M_ToBool(powerConditionsPage[mpStartOffset + 3] & BIT1);
                    powerConditions->idle_a.timerValid  = true;
                    powerConditions->idle_a.timerInHundredMillisecondIncrements = M_BytesTo4ByteValue(
                        powerConditionsPage[mpStartOffset + 4], powerConditionsPage[mpStartOffset + 5],
                        powerConditionsPage[mpStartOffset + 6], powerConditionsPage[mpStartOffset + 7]);
                    powerConditions->idle_a.restoreToDefault =
                        false; // turn this off now that we have the other settings stored.
                }
                if (powerConditions->standby_z.powerConditionValid && powerConditions->standby_z.restoreToDefault)
                {
                    powerConditions->standby_z.enableValid = true;
                    powerConditions->standby_z.enable      = M_ToBool(powerConditionsPage[mpStartOffset + 3] & BIT0);
                    powerConditions->standby_z.timerValid  = true;
                    powerConditions->standby_z.timerInHundredMillisecondIncrements = M_BytesTo4ByteValue(
                        powerConditionsPage[mpStartOffset + 8], powerConditionsPage[mpStartOffset + 9],
                        powerConditionsPage[mpStartOffset + 10], powerConditionsPage[mpStartOffset + 11]);
                    powerConditions->standby_z.restoreToDefault =
                        false; // turn this off now that we have the other settings stored.
                }
                if (powerConditions->powerModeBackgroundValid && powerConditions->powerModeBackgroundResetDefault)
                {
                    powerConditions->powerModeBackGroundRelationShip =
                        get_bit_range_uint8(powerConditionsPage[mpStartOffset + 2], 7, 6);
                    powerConditions->powerModeBackgroundResetDefault = false;
                }
                if (powerConditionsPage[mpStartOffset + 1] >
                    0x0A) // newer EPC drives support these, but older devices do not.
                {
                    if (powerConditions->idle_b.powerConditionValid && powerConditions->idle_b.restoreToDefault)
                    {
                        powerConditions->idle_b.enableValid = true;
                        powerConditions->idle_b.enable      = M_ToBool(powerConditionsPage[mpStartOffset + 3] & BIT2);
                        powerConditions->idle_b.timerValid  = true;
                        powerConditions->idle_b.timerInHundredMillisecondIncrements = M_BytesTo4ByteValue(
                            powerConditionsPage[mpStartOffset + 12], powerConditionsPage[mpStartOffset + 13],
                            powerConditionsPage[mpStartOffset + 14], powerConditionsPage[mpStartOffset + 15]);
                        powerConditions->idle_b.restoreToDefault =
                            false; // turn this off now that we have the other settings stored.
                    }
                    if (powerConditions->idle_c.powerConditionValid && powerConditions->idle_c.restoreToDefault)
                    {
                        powerConditions->idle_c.enableValid = true;
                        powerConditions->idle_c.enable      = M_ToBool(powerConditionsPage[mpStartOffset + 3] & BIT3);
                        powerConditions->idle_c.timerValid  = true;
                        powerConditions->idle_c.timerInHundredMillisecondIncrements = M_BytesTo4ByteValue(
                            powerConditionsPage[mpStartOffset + 16], powerConditionsPage[mpStartOffset + 17],
                            powerConditionsPage[mpStartOffset + 18], powerConditionsPage[mpStartOffset + 19]);
                        powerConditions->idle_c.restoreToDefault =
                            false; // turn this off now that we have the other settings stored.
                    }
                    if (powerConditions->standby_y.powerConditionValid && powerConditions->standby_y.restoreToDefault)
                    {
                        powerConditions->standby_y.enableValid = true;
                        powerConditions->standby_y.enable     = M_ToBool(powerConditionsPage[mpStartOffset + 2] & BIT0);
                        powerConditions->standby_y.timerValid = true;
                        powerConditions->standby_y.timerInHundredMillisecondIncrements = M_BytesTo4ByteValue(
                            powerConditionsPage[mpStartOffset + 20], powerConditionsPage[mpStartOffset + 21],
                            powerConditionsPage[mpStartOffset + 22], powerConditionsPage[mpStartOffset + 23]);
                        powerConditions->standby_y.restoreToDefault =
                            false; // turn this off now that we have the other settings stored.
                    }
                    // CCF fields
                    if (powerConditions->checkConditionFlags.ccfIdleValid &&
                        powerConditions->checkConditionFlags.ccfIdleResetDefault)
                    {
                        powerConditions->checkConditionFlags.ccfIdleMode =
                            get_bit_range_uint8(powerConditionsPage[mpStartOffset + 39], 7, 6);
                        powerConditions->checkConditionFlags.ccfIdleResetDefault = false;
                    }
                    if (powerConditions->checkConditionFlags.ccfStandbyValid &&
                        powerConditions->checkConditionFlags.ccfStandbyResetDefault)
                    {
                        powerConditions->checkConditionFlags.ccfStandbyMode =
                            get_bit_range_uint8(powerConditionsPage[mpStartOffset + 39], 5, 4);
                        powerConditions->checkConditionFlags.ccfStandbyResetDefault = false;
                    }
                    if (powerConditions->checkConditionFlags.ccfStopValid &&
                        powerConditions->checkConditionFlags.ccfStopResetDefault)
                    {
                        powerConditions->checkConditionFlags.ccfStopMode =
                            get_bit_range_uint8(powerConditionsPage[mpStartOffset + 39], 3, 2);
                        powerConditions->checkConditionFlags.ccfStopResetDefault = false;
                    }
                }
            }
            else
            {
                safe_free_aligned(&powerConditionsPage);
                return ret;
            }
            safe_free_aligned(&powerConditionsPage);
        }

        // Now, read the current settings mode page, make any necessary changes, then send it to the drive and we're
        // done.
        powerConditionsPageLength =
            MODE_PARAMETER_HEADER_10_LEN +
            MP_POWER_CONDITION_LEN; //*should* be maximum size we need assuming no block descriptor
        powerConditionsPage =
            M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(powerConditionsPageLength, sizeof(uint8_t),
                                                             device->os_info.minimumAlignment));
        if (powerConditionsPage == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        if (SUCCESS == (ret = scsi_Mode_Sense_10(device, MP_POWER_CONDTION, powerConditionsPageLength, 0, true, false,
                                                 MPC_CURRENT_VALUES, powerConditionsPage)))
        {
            // uint16_t modeDataLength = M_BytesTo2ByteValue(powerConditionsPage[0], powerConditionsPage[1]);
            uint16_t blockDescriptorLength =
                M_BytesTo2ByteValue(powerConditionsPage[MODE_HEADER_10_BLK_DESC_OFFSET],
                                    powerConditionsPage[MODE_HEADER_10_BLK_DESC_OFFSET + 1]);
            uint32_t mpStartOffset = MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength;
            // read the power conditions values that were requested
            if (powerConditions->idle_a.powerConditionValid && !powerConditions->idle_a.restoreToDefault)
            {
                if (powerConditions->idle_a.enableValid)
                {
                    if (powerConditions->idle_a.enable)
                    {
                        M_SET_BIT8(powerConditionsPage[mpStartOffset + 3], 1);
                    }
                    else
                    {
                        M_CLEAR_BIT8(powerConditionsPage[mpStartOffset + 3], 1);
                    }
                }
                if (powerConditions->idle_a.timerValid)
                {
                    powerConditionsPage[mpStartOffset + 4] =
                        M_Byte3(powerConditions->idle_a.timerInHundredMillisecondIncrements);
                    powerConditionsPage[mpStartOffset + 5] =
                        M_Byte2(powerConditions->idle_a.timerInHundredMillisecondIncrements);
                    powerConditionsPage[mpStartOffset + 6] =
                        M_Byte1(powerConditions->idle_a.timerInHundredMillisecondIncrements);
                    powerConditionsPage[mpStartOffset + 7] =
                        M_Byte0(powerConditions->idle_a.timerInHundredMillisecondIncrements);
                }
            }
            if (powerConditions->standby_z.powerConditionValid && !powerConditions->standby_z.restoreToDefault)
            {
                if (powerConditions->standby_z.enableValid)
                {
                    if (powerConditions->standby_z.enable)
                    {
                        M_SET_BIT8(powerConditionsPage[mpStartOffset + 3], 0);
                    }
                    else
                    {
                        M_CLEAR_BIT8(powerConditionsPage[mpStartOffset + 3], 0);
                    }
                }
                if (powerConditions->standby_z.timerValid)
                {
                    powerConditionsPage[mpStartOffset + 8] =
                        M_Byte3(powerConditions->standby_z.timerInHundredMillisecondIncrements);
                    powerConditionsPage[mpStartOffset + 9] =
                        M_Byte2(powerConditions->standby_z.timerInHundredMillisecondIncrements);
                    powerConditionsPage[mpStartOffset + 10] =
                        M_Byte1(powerConditions->standby_z.timerInHundredMillisecondIncrements);
                    powerConditionsPage[mpStartOffset + 11] =
                        M_Byte0(powerConditions->standby_z.timerInHundredMillisecondIncrements);
                }
            }
            if (powerConditions->powerModeBackgroundValid && !powerConditions->powerModeBackgroundResetDefault)
            {
                powerConditionsPage[mpStartOffset + 2] |= powerConditions->powerModeBackGroundRelationShip << 6;
            }
            if (powerConditionsPage[mpStartOffset + 1] >
                0x0A) // newer EPC drives support these, but older devices do not.
            {
                if (powerConditions->idle_b.powerConditionValid && !powerConditions->idle_b.restoreToDefault)
                {
                    if (powerConditions->idle_b.enableValid)
                    {
                        if (powerConditions->idle_b.enable)
                        {
                            M_SET_BIT8(powerConditionsPage[mpStartOffset + 3], 2);
                        }
                        else
                        {
                            M_CLEAR_BIT8(powerConditionsPage[mpStartOffset + 3], 2);
                        }
                    }
                    if (powerConditions->idle_b.timerValid)
                    {
                        powerConditionsPage[mpStartOffset + 12] =
                            M_Byte3(powerConditions->idle_b.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 13] =
                            M_Byte2(powerConditions->idle_b.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 14] =
                            M_Byte1(powerConditions->idle_b.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 15] =
                            M_Byte0(powerConditions->idle_b.timerInHundredMillisecondIncrements);
                    }
                }
                if (powerConditions->idle_c.powerConditionValid && !powerConditions->idle_c.restoreToDefault)
                {
                    if (powerConditions->idle_c.enableValid)
                    {
                        if (powerConditions->idle_c.enable)
                        {
                            M_SET_BIT8(powerConditionsPage[mpStartOffset + 3], 3);
                        }
                        else
                        {
                            M_CLEAR_BIT8(powerConditionsPage[mpStartOffset + 3], 3);
                        }
                    }
                    if (powerConditions->idle_c.timerValid)
                    {
                        powerConditionsPage[mpStartOffset + 16] =
                            M_Byte3(powerConditions->idle_c.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 17] =
                            M_Byte2(powerConditions->idle_c.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 18] =
                            M_Byte1(powerConditions->idle_c.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 19] =
                            M_Byte0(powerConditions->idle_c.timerInHundredMillisecondIncrements);
                    }
                }
                if (powerConditions->standby_y.powerConditionValid && !powerConditions->standby_y.restoreToDefault)
                {
                    if (powerConditions->standby_y.enableValid)
                    {
                        if (powerConditions->standby_y.enable)
                        {
                            M_SET_BIT8(powerConditionsPage[mpStartOffset + 2], 0);
                        }
                        else
                        {
                            M_CLEAR_BIT8(powerConditionsPage[mpStartOffset + 2], 0);
                        }
                    }
                    if (powerConditions->standby_y.timerValid)
                    {
                        powerConditionsPage[mpStartOffset + 20] =
                            M_Byte3(powerConditions->standby_y.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 21] =
                            M_Byte2(powerConditions->standby_y.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 22] =
                            M_Byte1(powerConditions->standby_y.timerInHundredMillisecondIncrements);
                        powerConditionsPage[mpStartOffset + 23] =
                            M_Byte0(powerConditions->standby_y.timerInHundredMillisecondIncrements);
                    }
                }
                // CCF fields
                if (powerConditions->checkConditionFlags.ccfIdleValid &&
                    !powerConditions->checkConditionFlags.ccfIdleResetDefault)
                {
                    powerConditionsPage[mpStartOffset + 39] |=
                        get_bit_range_uint8(powerConditions->checkConditionFlags.ccfIdleMode, 1, 0) << 6;
                }
                if (powerConditions->checkConditionFlags.ccfStandbyValid &&
                    !powerConditions->checkConditionFlags.ccfStandbyResetDefault)
                {
                    powerConditionsPage[mpStartOffset + 39] |=
                        get_bit_range_uint8(powerConditions->checkConditionFlags.ccfStandbyMode, 1, 0) << 4;
                }
                if (powerConditions->checkConditionFlags.ccfStopValid &&
                    !powerConditions->checkConditionFlags.ccfStopResetDefault)
                {
                    powerConditionsPage[mpStartOffset + 39] |=
                        get_bit_range_uint8(powerConditions->checkConditionFlags.ccfStopMode, 1, 0) << 2;
                }
            }
            // send the modified data to the drive
            ret = scsi_Mode_Select_10(device, powerConditionsPageLength, true, saveChanges, false, powerConditionsPage,
                                      powerConditionsPageLength);
            safe_free_aligned(&powerConditionsPage);
        }
        else
        {
            safe_free_aligned(&powerConditionsPage);
            return ret;
        }
        safe_free_aligned(&powerConditionsPage);
    }
    safe_free_aligned(&powerConditionsPage);
    return ret;
}

static eReturnValues scsi_Set_EPC_Power_Conditions(const tDevice*          device,
                                                   bool                    restoreAllToDefaults,
                                                   ptrPowerConditionTimers powerConditions,
                                                   bool                    saveChanges)
{
    return scsi_Set_Power_Conditions(device, restoreAllToDefaults, powerConditions, saveChanges);
}

// This function will go through and change each requested setting.
// The first failure that happens will cause the function to fail and not proceed to set any other timer values.
static eReturnValues ata_Set_EPC_Power_Conditions(const tDevice*          device,
                                                  bool                    restoreAllToDefaults,
                                                  ptrPowerConditionTimers powerConditions,
                                                  bool                    saveChanges)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word086)) &&
        le16_to_host(device->drive_info.IdentifyData.ata.Word086) & BIT15) // words 119, 120 valid
    {
        if (is_ATA_Identify_Word_Valid_With_Bits_14_And_15(le16_to_host(device->drive_info.IdentifyData.ata.Word119)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word119) & BIT7)
        {
            if (restoreAllToDefaults)
            {
                powerConditionSettings allSettings;
                safe_memset(&allSettings, sizeof(powerConditionSettings), 0, sizeof(powerConditionSettings));
                allSettings.powerConditionValid = true;
                allSettings.restoreToDefault    = true;
                ret = ata_Set_EPC_Power_Mode(device, PWR_CND_ALL, &allSettings, saveChanges);
            }
            else
            {
                if (powerConditions != M_NULLPTR)
                {
                    // go through each and every power condition and for each valid one, pass it to the ATA function. If
                    // unsuccessful, return immediately This does it in the same top-down order as the SCSI mode page to
                    // keep things working "the same" between the two
                    if (powerConditions->idle_a.powerConditionValid)
                    {
                        ret = ata_Set_EPC_Power_Mode(device, PWR_CND_IDLE_A, &powerConditions->idle_a, saveChanges);
                        if (ret != SUCCESS)
                        {
                            return ret;
                        }
                    }
                    if (powerConditions->standby_z.powerConditionValid)
                    {
                        ret =
                            ata_Set_EPC_Power_Mode(device, PWR_CND_STANDBY_Z, &powerConditions->standby_z, saveChanges);
                        if (ret != SUCCESS)
                        {
                            return ret;
                        }
                    }
                    if (powerConditions->idle_b.powerConditionValid)
                    {
                        ret = ata_Set_EPC_Power_Mode(device, PWR_CND_IDLE_B, &powerConditions->idle_b, saveChanges);
                        if (ret != SUCCESS)
                        {
                            return ret;
                        }
                    }
                    if (powerConditions->idle_c.powerConditionValid)
                    {
                        ret = ata_Set_EPC_Power_Mode(device, PWR_CND_IDLE_C, &powerConditions->idle_c, saveChanges);
                        if (ret != SUCCESS)
                        {
                            return ret;
                        }
                    }
                    if (powerConditions->standby_y.powerConditionValid)
                    {
                        ret =
                            ata_Set_EPC_Power_Mode(device, PWR_CND_STANDBY_Y, &powerConditions->standby_y, saveChanges);
                        if (ret != SUCCESS)
                        {
                            return ret;
                        }
                    }
                }
                else
                {
                    ret = BAD_PARAMETER;
                }
            }
        }
    }
    return ret;
}

eReturnValues set_EPC_Power_Conditions(const tDevice*          device,
                                       bool                    restoreAllToDefaults,
                                       ptrPowerConditionTimers powerConditions,
                                       bool                    saveChanges)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Set_EPC_Power_Conditions(device, restoreAllToDefaults, powerConditions, saveChanges);
    }
    else
    {
        // assume and try SCSI method
        ret = scsi_Set_EPC_Power_Conditions(device, restoreAllToDefaults, powerConditions, saveChanges);
    }
    return ret;
}

eReturnValues get_Power_State(const tDevice* device, uint32_t* powerState, eFeatureModeSelect selectValue)
{
    eReturnValues ret = UNKNOWN;
    if (device->drive_info.drive_type == NVME_DRIVE)
    {
        nvmeFeaturesCmdOpt cmdOpts;
        safe_memset(&cmdOpts, sizeof(nvmeFeaturesCmdOpt), 0, sizeof(nvmeFeaturesCmdOpt));
        switch (selectValue)
        {
        case CURRENT_VALUE:
            cmdOpts.fid = NVME_FEAT_POWER_MGMT_;
            cmdOpts.sel = NVME_CURRENT_FEAT_SEL;
            ret         = nvme_Get_Features(device, &cmdOpts);
            if (ret == SUCCESS)
            {
                *powerState = cmdOpts.featSetGetValue;
            }
            break;
        case DEFAULT_VALUE:
        case SAVED_VALUE:
        case CAPABILITIES:
        case CHANGEABLE_VALUE:
        default:
            if (VERBOSITY_QUIET < device->deviceVerbosity)
            {
                printf("Power State=0x%x is currently not supported on this device.\n", selectValue);
            }
            ret = NOT_SUPPORTED;
            break;
        }
    }
    else
    {
        if (VERBOSITY_QUIET < device->deviceVerbosity)
        {
            print_str("Get Power State is currently not supported on this device type at this time.\n");
        }
        ret = NOT_SUPPORTED;
    }

    return ret;
}

static eReturnValues ata_Get_Power_Consumption_Identifiers(const tDevice*                 device,
                                                           ptrPowerConsumptionIdentifiers identifiers)
{
    eReturnValues ret = NOT_SUPPORTED;

    DISABLE_NONNULL_COMPARE
    if (identifiers == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }
    RESTORE_NONNULL_COMPARE
    bool     featureSupported        = false;
    uint32_t powerConsumptionLogSize = LEGACY_DRIVE_SEC_SIZE; // from ATA Spec
    uint8_t* powerConsumptionLog =
        C_CAST(uint8_t*, safe_calloc_aligned(powerConsumptionLogSize * sizeof(uint8_t), sizeof(uint8_t),
                                             device->os_info.minimumAlignment));
    if (powerConsumptionLog == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }

    // read 30h for IDENTIFY DEVICE data log with page 03h for support capabilities
    ret = send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA, 0x03, powerConsumptionLog,
                                    LEGACY_DRIVE_SEC_SIZE, 0);
    if (SUCCESS == ret)
    {
        uint64_t qword1 = M_BytesTo8ByteValue(powerConsumptionLog[15], powerConsumptionLog[14], powerConsumptionLog[13],
                                              powerConsumptionLog[12], powerConsumptionLog[11], powerConsumptionLog[10],
                                              powerConsumptionLog[9], powerConsumptionLog[8]);
        if ((qword1 & ATA_ID_DATA_QWORD_VALID_BIT) && (qword1 & BIT59))
        {
            featureSupported = true;
        }
    }
    else
    {
        safe_free_aligned(&powerConsumptionLog);
        return ret;
    }

    if (!featureSupported)
    {
        // no need to read other pages, return from here
        safe_free_aligned(&powerConsumptionLog);
        return NOT_SUPPORTED;
    }

    // read 30h for IDENTIFY DEVICE data log with page 04h for current settings
    memset(powerConsumptionLog, 0, LEGACY_DRIVE_SEC_SIZE);
    ret = send_ATA_Read_Log_Ext_Cmd(device, ATA_LOG_IDENTIFY_DEVICE_DATA, 0x04, powerConsumptionLog,
                                    LEGACY_DRIVE_SEC_SIZE, 0);
    if (SUCCESS == ret)
    {
        uint64_t qword1 = M_BytesTo8ByteValue(powerConsumptionLog[15], powerConsumptionLog[14], powerConsumptionLog[13],
                                              powerConsumptionLog[12], powerConsumptionLog[11], powerConsumptionLog[10],
                                              powerConsumptionLog[9], powerConsumptionLog[8]);
        if ((qword1 & ATA_ID_DATA_QWORD_VALID_BIT) && (qword1 & BIT24))
        {
            identifiers->currentIdentifierValid = true;

            uint64_t controlSettings = M_BytesTo8ByteValue(
                powerConsumptionLog[95], powerConsumptionLog[94], powerConsumptionLog[93], powerConsumptionLog[92],
                powerConsumptionLog[91], powerConsumptionLog[90], powerConsumptionLog[89], powerConsumptionLog[88]);
            if (controlSettings & ATA_ID_DATA_QWORD_VALID_BIT)
            {

                uint16_t defaultSetting                            = get_16bit_range_uint64(controlSettings, 31, 16);
                identifiers->defaultControlField.controlIdentifier = get_8bit_range_uint16(defaultSetting, 7, 0);
                identifiers->defaultControlField.activeLevel       = get_8bit_range_uint16(defaultSetting, 9, 8);

                uint16_t currentSetting                            = get_16bit_range_uint64(controlSettings, 15, 0);
                identifiers->currentControlField.controlIdentifier = get_8bit_range_uint16(currentSetting, 7, 0);
                identifiers->currentControlField.activeLevel       = get_8bit_range_uint16(currentSetting, 9, 8);
            }
        }
    }
    else
    {
        safe_free_aligned(&powerConsumptionLog);
        return ret;
    }

    // read 59h for Power Consumption Control log
    memset(powerConsumptionLog, 0, LEGACY_DRIVE_SEC_SIZE);
    ret = get_ATA_Log(device, ATA_LOG_POWER_CONSUMPTION_CONTROL_LOG, M_NULLPTR, M_NULLPTR, true, false, true,
                      powerConsumptionLog, powerConsumptionLogSize, M_NULLPTR, powerConsumptionLogSize, 0);
    if (SUCCESS == ret)
    {
        identifiers->numberOfPCIdentifiers =
            C_CAST(uint8_t, M_BytesTo8ByteValue(powerConsumptionLog[7], powerConsumptionLog[6], powerConsumptionLog[5],
                                                powerConsumptionLog[4], powerConsumptionLog[3], powerConsumptionLog[2],
                                                powerConsumptionLog[1], powerConsumptionLog[0]));
        uint32_t pcIter  = UINT32_C(8);
        uint32_t counter = UINT32_C(0);

        for (; pcIter < powerConsumptionLogSize && pcIter <= C_CAST(uint32_t, identifiers->numberOfPCIdentifiers * 8);
             pcIter += 8, counter++)
        {
            identifiers->identifiers[counter].value =
                M_BytesTo2ByteValue(powerConsumptionLog[pcIter], powerConsumptionLog[pcIter + 1]);
            identifiers->identifiers[counter].units           = powerConsumptionLog[pcIter + 2] & 0x07;
            identifiers->identifiers[counter].identifierValue = powerConsumptionLog[pcIter + 3];
        }
    }
    else
    {
        safe_free_aligned(&powerConsumptionLog);
        return ret;
    }

    safe_free_aligned(&powerConsumptionLog);

    return ret;
}

static eReturnValues scsi_Get_Power_Consumption_Identifiers(const tDevice*                 device,
                                                            ptrPowerConsumptionIdentifiers identifiers)
{
    eReturnValues ret                    = NOT_SUPPORTED;
    uint32_t      powerConsumptionLength = UINT32_C(0);
    if (SUCCESS == get_SCSI_VPD_Page_Size(device, POWER_CONSUMPTION, &powerConsumptionLength))
    {
        uint8_t* powerConsumptionPage = C_CAST(
            uint8_t*, safe_calloc_aligned(powerConsumptionLength, sizeof(uint8_t), device->os_info.minimumAlignment));
        if (powerConsumptionPage == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        if (SUCCESS ==
            scsi_Inquiry(device, powerConsumptionPage, powerConsumptionLength, POWER_CONSUMPTION, true, false))
        {
            ret = SUCCESS;
            // now get all the power consumption descriptors into the struct
            identifiers->numberOfPCIdentifiers = C_CAST(uint8_t, (powerConsumptionLength - 4) / 4);
            uint32_t pcIter                    = UINT32_C(4);
            uint32_t counter                   = UINT32_C(0);
            // ctc changed the "<" conditions to "<=" so all the identifiers get parsed (was an "off-by-1" problem")
            for (;
                 pcIter <= powerConsumptionLength && pcIter <= C_CAST(uint32_t, identifiers->numberOfPCIdentifiers * 4);
                 pcIter += 4, counter++)
            {
                identifiers->identifiers[counter].identifierValue = powerConsumptionPage[pcIter];
                identifiers->identifiers[counter].units           = powerConsumptionPage[pcIter + 1] & 0x07;
                identifiers->identifiers[counter].value =
                    M_BytesTo2ByteValue(powerConsumptionPage[pcIter + 2], powerConsumptionPage[pcIter + 3]);
            }
        }
        else
        {
            ret = FAILURE;
        }
        safe_free_aligned(&powerConsumptionPage);
    }
    if (ret != FAILURE)
    {
        uint8_t* pcModePage =
            M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(MODE_PARAMETER_HEADER_10_LEN + 16, sizeof(uint8_t),
                                                             device->os_info.minimumAlignment));
        if (pcModePage == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
        // read changable value to see if active field can be modified
        if (SUCCESS == scsi_Mode_Sense_10(device, MP_POWER_CONSUMPTION, MODE_PARAMETER_HEADER_10_LEN + 16, 0x01, true,
                                          false, MPC_CHANGABLE_VALUES, pcModePage))
        {
            if (get_bit_range_uint8(pcModePage[MODE_PARAMETER_HEADER_10_LEN + 6], 2, 0) > 0)
            {
                identifiers->activeLevelChangable = true;
            }
            else
            {
                identifiers->activeLevelChangable = false;
            }
        }
        // read the mode page to get the default identifier.
        if (SUCCESS == scsi_Mode_Sense_10(device, MP_POWER_CONSUMPTION, MODE_PARAMETER_HEADER_10_LEN + 16, 0x01, true,
                                          false, MPC_DEFAULT_VALUES, pcModePage))
        {
            // check the active level to make sure it is zero
            identifiers->defaultControlField.activeLevel = pcModePage[MODE_PARAMETER_HEADER_10_LEN + 6] & 0x07;
            if (identifiers->defaultControlField.activeLevel == 0)
            {
                // ctc 10 lines of code after the comments are necessary because the powerConsumptionIdentifier and
                // the identfiers->identifiers[] are NOT necessarily in order ctc need to step through the
                // indentifiers to find the correct one
                uint8_t powerConsumptionIdentifier = pcModePage[MODE_PARAMETER_HEADER_10_LEN + 7];
                uint8_t counter                    = UINT8_C(0);
                for (; counter < identifiers->numberOfPCIdentifiers; counter++)
                {
                    if (identifiers->identifiers[counter].identifierValue == powerConsumptionIdentifier)
                    {
                        identifiers->defaultControlField.controlIdentifier = counter;
                    }
                }
            }
        }
        // read the mode page to get the current identifier.
        if (SUCCESS == scsi_Mode_Sense_10(device, MP_POWER_CONSUMPTION, MODE_PARAMETER_HEADER_10_LEN + 16, 0x01, true,
                                          false, MPC_CURRENT_VALUES, pcModePage))
        {
            ret = SUCCESS;
            // check the active level to make sure it is zero
            identifiers->currentControlField.activeLevel = pcModePage[MODE_PARAMETER_HEADER_10_LEN + 6] & 0x07;
            if (identifiers->currentControlField.activeLevel == 0)
            {
                identifiers->currentIdentifierValid = true;
                // ctc 10 lines of code after the comments are necessary because the powerConsumptionIdentifier and
                // the identfiers->identifiers[] are NOT necessarily in order ctc need to step through the
                // indentifiers to find the correct one
                uint8_t powerConsumptionIdentifier = pcModePage[MODE_PARAMETER_HEADER_10_LEN + 7];
                uint8_t counter                    = UINT8_C(0);
                for (; counter < identifiers->numberOfPCIdentifiers; counter++)
                {
                    if (identifiers->identifiers[counter].identifierValue == powerConsumptionIdentifier)
                    {
                        identifiers->currentControlField.controlIdentifier = counter;
                    }
                }
            }
        }
        else
        {
            ret = NOT_SUPPORTED;
        }
        safe_free_aligned(&pcModePage);
    }
    return ret;
}

eReturnValues get_Power_Consumption_Identifiers(const tDevice* device, ptrPowerConsumptionIdentifiers identifiers)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Get_Power_Consumption_Identifiers(device, identifiers);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        ret = scsi_Get_Power_Consumption_Identifiers(device, identifiers);
    }
    return ret;
}

static void ata_Print_Power_Consumption_Identifiers(ptrPowerConsumptionIdentifiers identifiers)
{
    DISABLE_NONNULL_COMPARE
    if (identifiers != M_NULLPTR)
    {
        if (identifiers->currentIdentifierValid)
        {
            if (identifiers->currentControlField.activeLevel == 0)
            {
                // valid controller identifier in logpage 59h
                if (identifiers->numberOfPCIdentifiers > 0)
                {
                    double currentConsumption =
                        identifiers->identifiers[identifiers->currentControlField.controlIdentifier].value;
                    uint8_t currentUnit =
                        identifiers->identifiers[identifiers->currentControlField.controlIdentifier].units;
#define POWER_CONSUMPTION_UNIT_BUFFER_LENGTH 25
                    DECLARE_ZERO_INIT_ARRAY(char, unitBuff, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH);
                    char* currentUnits = &unitBuff[0];
                    // convert this to a smaller value that can be reprsented with minimal floating point
                    // (13500mw->13.5w)
                    while ((currentConsumption / 1000.0) > 1 && currentUnit > 0)
                    {
                        currentConsumption /= 1000.0;
                        --currentUnit; // change the unit
                    }

                    // now print the units
                    switch (currentUnit)
                    {
                    case 3: // watts
                        snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Watts");
                        break;
                    case 4: // milliwatts
                        snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Milliwatts");
                        break;
                    case 5: // microwatts
                        snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Microwatts");
                        break;
                    default:
                        snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH,
                                            "unknown unit of measure");
                        break;
                    }
                    printf("Current Power Consumption Value: %g %s\n", currentConsumption, currentUnits);
                }
                // even though active level is set to control identifier, logpage 59h is not returning valid identifier,
                // this has been observed on STX drive - Nidhi
                else
                {
                    print_str("Drive is currently configured with unknown settings!\n");
                }
            }
            else
            {
                // high medium low value
                print_str("Drive is currently configured with ");
                switch (identifiers->currentControlField.activeLevel)
                {
                case 1:
                    print_str("highest relative active power consumption\n");
                    break;
                case 2:
                    print_str("intermediate relative active power consumption\n");
                    break;
                case 3:
                    print_str("lowest relative active power consumption\n");
                    break;
                default:
                    print_str("unknown active level!\n");
                    break;
                }
            }

            // show a list of the values supported (in watts). If the value is less than 1 watt, exclude it
            print_str("Supported Max Power Consumption Set Points");
            if (identifiers->numberOfPCIdentifiers > 0)
                print_str(" (Watts)");
            print_str(": \n\t[");
            uint8_t pcIter = UINT8_C(0);
            for (; pcIter < identifiers->numberOfPCIdentifiers; pcIter++)
            {
                double watts = identifiers->identifiers[pcIter].value;
                switch (identifiers->identifiers[pcIter].units)
                {
                case 3: // watts
                    break;
                case 4: // milliwatts
                    // ctc properly round milliwatts values
                    watts /= 1000.0;
                    break;
                case 5: // microwatts
                    // ctc properly round milliwatts values
                    watts /= 1000000.0;
                    break;
                default:
                    continue; // continue the for loop
                }
                printf(" %g |", watts); // use %g to use shortest possible notation for the output. This keeps 12w 13.5w
                                        // without extra zeros all over the place
            }
            // now print default, highest, lowest, and intermediate
            print_str(" highest | intermediate | lowest | default ]\n");
        }
        else
        {
            print_str("Power Consumption Control feature is currently disabled on drive.\n");
        }
    }
    RESTORE_NONNULL_COMPARE
}

static void scsi_Print_Power_Consumption_Identifiers(ptrPowerConsumptionIdentifiers identifiers)
{

    if (identifiers != M_NULLPTR)
    {
        if (identifiers->numberOfPCIdentifiers > 0)
        {
            // show the current value
            if (identifiers->currentIdentifierValid)
            {
                double currentConsumption =
                    identifiers->identifiers[identifiers->currentControlField.controlIdentifier].value;
                uint8_t currentUnit =
                    identifiers->identifiers[identifiers->currentControlField.controlIdentifier].units;
#define POWER_CONSUMPTION_UNIT_BUFFER_LENGTH 25
                DECLARE_ZERO_INIT_ARRAY(char, unitBuff, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH);
                char* currentUnits = &unitBuff[0];
                // convert this to a smaller value that can be reprsented with minimal floating point (13500mw->13.5w)
                while ((currentConsumption / 1000.0) > 1 && currentUnit > 0)
                {
                    currentConsumption /= 1000.0;
                    --currentUnit; // change the unit
                }

                // now print the units
                switch (currentUnit)
                {
                case 0: // gigawatts
                    snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Gigawatts");
                    break;
                case 1: // megawatts
                    snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Megawatts");
                    break;
                case 2: // kilowatts
                    snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Kilowatts");
                    break;
                case 3: // watts
                    snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Watts");
                    break;
                case 4: // milliwatts
                    snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Milliwatts");
                    break;
                case 5: // microwatts
                    snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "Microwatts");
                    break;
                default:
                    snprintf_err_handle(currentUnits, POWER_CONSUMPTION_UNIT_BUFFER_LENGTH, "unknown unit of measure");
                    break;
                }
                printf("Current Power Consumption Value: %g %s\n", currentConsumption, currentUnits);
            }
            else
            {
                // high medium low value
                print_str("Drive is currently configured with ");
                switch (identifiers->currentControlField.activeLevel)
                {
                case 1:
                    print_str("highest relative active power consumption\n");
                    break;
                case 2:
                    print_str("intermediate relative active power consumption\n");
                    break;
                case 3:
                    print_str("lowest relative active power consumption\n");
                    break;
                default:
                    print_str("unknown active level!\n");
                    break;
                }
            }
            // show a list of the values supported (in watts). If the value is less than 1 watt, exclude it
            print_str("Supported Max Power Consumption Set Points (Watts): \n\t[");
            uint8_t pcIter = UINT8_C(0);
            for (; pcIter < identifiers->numberOfPCIdentifiers; pcIter++)
            {
                double watts = identifiers->identifiers[pcIter].value;
                switch (identifiers->identifiers[pcIter].units)
                {
                case 0: // gigawatts
                    watts *= 1000000000.0;
                    break;
                case 1: // megawatts
                    watts *= 1000000.0;
                    break;
                case 2: // kilowatts
                    watts *= 1000.0;
                    break;
                case 3: // watts
                    break;
                case 4: // milliwatts
                    // ctc properly round milliwatts values
                    watts /= 1000.0;
                    break;
                case 5: // microwatts
                    // ctc properly round milliwatts values
                    watts /= 1000000.0;
                    break;
                default:
                    continue; // continue the for loop
                }
                printf(" %g |", watts); // use %g to use shortest possible notation for the output. This keeps 12w 13.5w
                                        // without extra zeros all over the place
            }
            if (identifiers->activeLevelChangable)
            {
                // now print default, highest, lowest, and intermediate
                print_str(" highest | intermediate | lowest |");
            }
            print_str(" default ]\n"); // always allow default so that we can restore back to original settings
        }
        else
        {
            // high medium low value
            print_str("Drive is currently configured with ");
            switch (identifiers->currentControlField.activeLevel)
            {
            case 0:
                printf("Power consumption identifier set to %" PRIu8 "\n",
                       identifiers->currentControlField.controlIdentifier);
                break;
            case 1:
                print_str("highest relative active power consumption\n");
                break;
            case 2:
                print_str("intermediate relative active power consumption\n");
                break;
            case 3:
                print_str("lowest relative active power consumption\n");
                break;
            default:
                print_str("unknown active level!\n");
                break;
            }
            print_str("Supported Max Power Consumption Set Points : \n\t[ ");
            if (identifiers->activeLevelChangable)
            {
                // now print default, highest, lowest, and intermediate
                print_str(" highest | intermediate | lowest |");
            }
            print_str(" default ]\n"); // always allow default so that we can restore back to original settings
        }
    }
}

void print_Power_Consumption_Identifiers(const tDevice* device, ptrPowerConsumptionIdentifiers identifiers)
{
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ata_Print_Power_Consumption_Identifiers(identifiers);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        scsi_Print_Power_Consumption_Identifiers(identifiers);
    }
}

static eReturnValues ata_Set_Power_Consumption(const tDevice* device,
                                               ePCActiveLevel activeLevelField,
                                               uint8_t        powerConsumptionIdentifier,
                                               bool           resetToDefault,
                                               bool           disableFeature)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (disableFeature)
    {
        ret = ata_SF_Power_Consumption(device, false, false, 0, 0);
    }
    else
    {
        ret = ata_SF_Power_Consumption(device, resetToDefault, true, activeLevelField, powerConsumptionIdentifier);

        if (ret != SUCCESS)
        {
            ret = NOT_SUPPORTED;
        }
    }

    return ret;
}

static eReturnValues scsi_Set_Power_Consumption(const tDevice* device,
                                                ePCActiveLevel activeLevelField,
                                                uint8_t        powerConsumptionIdentifier,
                                                bool           resetToDefault)
{
    eReturnValues ret = NOT_SUPPORTED;

    uint8_t* pcModePage =
        M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(16 + MODE_PARAMETER_HEADER_10_LEN, sizeof(uint8_t),
                                                         device->os_info.minimumAlignment));
    eScsiModePageControl mpControl = MPC_CURRENT_VALUES;
    if (pcModePage == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }
    if (resetToDefault)
    {
        mpControl = MPC_DEFAULT_VALUES;
    }
    if (SUCCESS == scsi_Mode_Sense_10(device, MP_POWER_CONSUMPTION, 16 + MODE_PARAMETER_HEADER_10_LEN, 0x01, true,
                                      false, mpControl, pcModePage))
    {
        if (!resetToDefault)
        {
            // modify the value we want to set
            switch (activeLevelField)
            {
            case PC_ACTIVE_LEVEL_IDENTIFIER:
                // set active level to 0
                pcModePage[MODE_PARAMETER_HEADER_10_LEN + 6] &= 0xFC; // clear lower 2 bits to 0
                // set the power consumption identifier we were given
                pcModePage[MODE_PARAMETER_HEADER_10_LEN + 7] = powerConsumptionIdentifier;
                break;
            case PC_ACTIVE_LEVEL_HIGHEST:
            case PC_ACTIVE_LEVEL_INTERMEDIATE:
            case PC_ACTIVE_LEVEL_LOWEST:
                // set the active level to what was requested (power consumption identifier is ignored here)
                pcModePage[MODE_PARAMETER_HEADER_10_LEN + 6] &= 0xFC; // clear lower 2 bits to 0
                // now set it now that the bits are cleared out
                pcModePage[MODE_PARAMETER_HEADER_10_LEN + 6] |= C_CAST(uint8_t, activeLevelField);
                break;
            default:
                ret = FAILURE;
                break;
            }
        }
        if (ret != FAILURE)
        {
            // now do mode select with the data for the mode to set
            ret = scsi_Mode_Select_10(device, 16 + MODE_PARAMETER_HEADER_10_LEN, true, true, false, pcModePage,
                                      16 + MODE_PARAMETER_HEADER_10_LEN);
        }
    }
    safe_free_aligned(&pcModePage);

    return ret;
}

eReturnValues set_Power_Consumption(const tDevice* device,
                                    ePCActiveLevel activeLevelField,
                                    uint8_t        powerConsumptionIdentifier,
                                    bool           resetToDefault,
                                    bool           disableFeature)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Set_Power_Consumption(device, activeLevelField, powerConsumptionIdentifier, resetToDefault,
                                        disableFeature);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        ret = scsi_Set_Power_Consumption(device, activeLevelField, powerConsumptionIdentifier, resetToDefault);
    }
    return ret;
}

eReturnValues map_Watt_Value_To_Power_Consumption_Identifier(const tDevice* device,
                                                             double         watts,
                                                             uint8_t*       powerConsumptionIdentifier)
{
    eReturnValues               ret = NOT_SUPPORTED;
    powerConsumptionIdentifiers identifiers;
    safe_memset(&identifiers, sizeof(powerConsumptionIdentifiers), 0, sizeof(powerConsumptionIdentifiers));
    *powerConsumptionIdentifier = 0xFF; // invalid

    ret = get_Power_Consumption_Identifiers(device, &identifiers);

    /*
    This is a dummied up test to make sure this code REALLY REALLY works by putting these in a random order (since
    order is not specified in the SPC specification)
    ret = SUCCESS;
    identifiers.currentIdentifier = 0;
    identifiers.numberOfpowerConsumptionIdentifiers = 4;
    identifiers.identifiers[0].identifierValue = 3;
    identifiers.identifiers[0].units = 3;//watts
    identifiers.identifiers[0].value = 4;
    identifiers.identifiers[1].identifierValue = 0;
    identifiers.identifiers[1].units = 3;//watts
    identifiers.identifiers[1].value = 8;
    identifiers.identifiers[2].identifierValue = 1;
    identifiers.identifiers[2].units = 3;//watts
    identifiers.identifiers[2].value = 6;
    identifiers.identifiers[3].identifierValue = 4;
    identifiers.identifiers[3].units = 3;//watts
    identifiers.identifiers[3].value = 2;
    */

    if (ret == SUCCESS)
    {
        if (identifiers.numberOfPCIdentifiers == 0) // to handle case when no descriptors are available
            return BAD_PARAMETER;

        // ctc one line code change follows
        uint64_t roundedWatts = C_CAST(uint64_t, watts + 0.5);

        bool exactMatchFound = false;
        // now map the watt value to a power consumption identifier
        // ctc had to change variable initialization of iter1 and inter2 to match now-nested for loops
        uint8_t  iter1  = UINT8_C(0);
        uint8_t  iter2  = UINT8_C(0);
        uint8_t  pcId1  = UINT8_C(0xFF);
        uint8_t  pcId2  = UINT8_C(0xFF);
        uint64_t watts1 = UINT64_C(0);
        uint64_t watts2 = UINT64_C(0);

        ret = BAD_PARAMETER;
        // ctc changed to nested for loops here... not sure it's needed, but it's clearer
        //         for (; iter1 < identifiers.numberOfpowerConsumptionIdentifiers /* && iter2 >= 0*/; iter1++,
        //         iter2--)
        for (; iter1 < identifiers.numberOfPCIdentifiers; iter1++)
        {
            // ctc needed to reset iter2=0 to go through the for loop the next times... not sure why the code
            // doesn't follow convention ctc and use for(initializer, condition, increment), but whatever.
            // Nonstandard and goofy coding sytle, I guess
            iter2 = 0;
            for (; iter2 < identifiers.numberOfPCIdentifiers; iter2++)
            {
                uint64_t pcWatts1 = identifiers.identifiers[iter1].value;
                uint64_t pcWatts2 = identifiers.identifiers[iter2].value;
                // convert based on the units!
                switch (identifiers.identifiers[iter1].units)
                {
                case 0: // gigawatts
                    pcWatts1 *= UINT64_C(1000000000);
                    break;
                case 1: // megawatts
                    pcWatts1 *= UINT64_C(1000000);
                    break;
                case 2: // kilowatts
                    pcWatts1 *= UINT64_C(1000);
                    break;
                case 3: // watts
                    break;
                case 4: // milliwatts
                    // ctc properly round milliwatts values
                    pcWatts1 = (pcWatts1 + UINT64_C(500)) / UINT64_C(1000);
                    break;
                case 5: // microwatts
                    // ctc properly round microwatts values
                    pcWatts1 = (pcWatts1 + UINT64_C(500000)) / UINT64_C(1000000);
                    break;
                default:
                    ret = NOT_SUPPORTED;
                    break;
                }
                // ctc change code line below to switch on [iter2] instead of [iter1]
                switch (identifiers.identifiers[iter2].units)
                {
                case 0: // gigawatts
                    pcWatts2 *= UINT64_C(1000000000);
                    break;
                case 1: // megawatts
                    pcWatts2 *= UINT64_C(1000000);
                    break;
                case 2: // kilowatts
                    pcWatts2 *= UINT64_C(1000);
                    break;
                case 3: // watts
                    break;
                case 4: // milliwatts
                    // ctc properly round milliwatts values
                    pcWatts2 = (pcWatts2 + UINT64_C(500)) / UINT64_C(1000);
                    break;
                case 5: // microwatts
                    // ctc properly round microwatts values
                    pcWatts2 = (pcWatts2 + UINT64_C(500000)) / UINT64_C(1000000);
                    break;
                default:
                    ret = NOT_SUPPORTED;
                    break;
                }
                if (pcWatts1 <= roundedWatts)
                {
                    if (watts - C_CAST(double, watts1) > watts - C_CAST(double, pcWatts1))
                    {
                        pcId1  = identifiers.identifiers[iter1].identifierValue;
                        watts1 = pcWatts1;
                        if (pcWatts1 == roundedWatts)
                        {
                            ret                         = SUCCESS;
                            exactMatchFound             = true;
                            *powerConsumptionIdentifier = identifiers.identifiers[iter1].identifierValue;
                            break;
                        }
                    }
                }
                if (pcWatts2 <= roundedWatts)
                {
                    if (watts - C_CAST(double, watts2) > watts - C_CAST(double, pcWatts2))
                    {
                        pcId2  = identifiers.identifiers[iter2].identifierValue;
                        watts2 = pcWatts2;
                        if (pcWatts2 == roundedWatts)
                        {
                            ret                         = SUCCESS;
                            exactMatchFound             = true;
                            *powerConsumptionIdentifier = identifiers.identifiers[iter2].identifierValue;
                            break;
                        }
                    }
                }
            }
        }

        if (!exactMatchFound)
        {
            // now compare the best results between the two iterators to see which is closer to the best match, or
            // is the best match need to check which one is closer and select it
            if (watts - C_CAST(double, watts1) >= watts - C_CAST(double, watts2))
            {
                ret                         = SUCCESS;
                *powerConsumptionIdentifier = pcId2;
            }
            else if (watts - C_CAST(double, watts1) <= watts - C_CAST(double, watts2))
            {
                ret                         = SUCCESS;
                *powerConsumptionIdentifier = pcId1;
            }
        }
    }
    return ret;
}

eReturnValues enable_Disable_APM_Feature(const tDevice* device, bool enable)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        // check the identify bits to make sure APM is supported.
        if (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word083)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word083) & BIT3)
        {
            if (enable)
            {
                // set value to 0x7F when requesting an enable operation so that it's a good mix of
                // performance and power savings.
                ret = ata_SF_APM(device, ATA_SF_ENABLE, 0x7F);
            }
            else
            {
                ret = ata_SF_APM(device, ATA_SF_DISABLE, RESERVED);
                if (ret != SUCCESS)
                {
                    // the disable APM feature is not available on all devices according to ATA spec.
                    ret = NOT_SUPPORTED;
                }
            }
        }
    }
    return ret;
}
// APM Levels:
//  0 - reserved
//  1 = minimum power consumption with standby mode
//  2-7Fh = Intermedia power management levels with standby mode
//  80h = minimum power consumption without standby mode
//  81h - FDh = intermediate power management levels without standby mode
//  FEh = maximum performance.
eReturnValues set_APM_Level(const tDevice* device, uint8_t apmLevel)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        // check the identify bits to make sure APM is supported.
        if (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word083)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word083) & BIT3)
        {
            // subcommand 05 with the apmLevel in the count field
            ret = ata_SF_APM(device, ATA_SF_ENABLE, apmLevel);
        }
    }
    return ret;
}

eReturnValues get_APM_Level(const tDevice* device, uint8_t* apmLevel)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        // check the identify bits to make sure APM is supported.
        if (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word083)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word083) & BIT3)
        {
            // get it from identify device word 91
            ret = SUCCESS;
            if (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word091)))
            {
                *apmLevel = M_Byte0(device->drive_info.IdentifyData.ata.Word091);
            }
            else
            {
                *apmLevel = UINT8_MAX; // invalid value
            }
        }
    }
    return ret;
}

static eReturnValues ata_Get_EPC_Settings(const tDevice* device, ptrEpcSettings epcSettings)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (epcSettings == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    uint32_t epcLogSize = LEGACY_DRIVE_SEC_SIZE * 2; // from ATA Spec
    // get_ATA_Log_Size(device, ATA_LOG_POWER_CONDITIONS, &epcLogSize, true, false) //uncomment this line to ask the
    // drive for the EPC log size rather than use the hard coded value above.
    uint8_t* epcLog = C_CAST(
        uint8_t*, safe_calloc_aligned(epcLogSize * sizeof(uint8_t), sizeof(uint8_t), device->os_info.minimumAlignment));
    if (epcLog == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }
    if (SUCCESS == get_ATA_Log(device, ATA_LOG_POWER_CONDITIONS, M_NULLPTR, M_NULLPTR, true, false, true, epcLog,
                               epcLogSize, M_NULLPTR, epcLogSize, 0))
    {
        ret = SUCCESS;
        for (uint32_t offset = UINT32_C(0); offset < (LEGACY_DRIVE_SEC_SIZE * 2); offset += 64)
        {
            ptrPowerConditionInfo currentPowerCondition = M_NULLPTR;
            switch (offset)
            {
            case 0: // idle a
                currentPowerCondition = &epcSettings->idle_a;
                break;
            case 64: // idle b
                currentPowerCondition = &epcSettings->idle_b;
                break;
            case 128: // idle c
                currentPowerCondition = &epcSettings->idle_c;
                break;
            case LEGACY_DRIVE_SEC_SIZE + 384: // standby y
                currentPowerCondition = &epcSettings->standby_y;
                break;
            case LEGACY_DRIVE_SEC_SIZE + 448: // standby z
                currentPowerCondition = &epcSettings->standby_z;
                break;
            default:
                continue;
            }
            if (currentPowerCondition == M_NULLPTR)
            {
                continue;
            }
            if (epcLog[offset + 1] & BIT7)
            {
                // set the bits
                currentPowerCondition->powerConditionSupported = true;
                if (epcLog[offset + 1] & BIT6)
                {
                    currentPowerCondition->powerConditionSaveable = true;
                }
                if (epcLog[offset + 1] & BIT5)
                {
                    currentPowerCondition->powerConditionChangeable = true;
                }
                if (epcLog[offset + 1] & BIT4)
                {
                    currentPowerCondition->defaultTimerEnabled = true;
                }
                if (epcLog[offset + 1] & BIT3)
                {
                    currentPowerCondition->savedTimerEnabled = true;
                }
                if (epcLog[offset + 1] & BIT2)
                {
                    currentPowerCondition->currentTimerEnabled = true;
                }
                if (epcLog[offset + 1] & BIT1)
                {
                    currentPowerCondition->holdPowerConditionNotSupported = true;
                }
                // set the values
                currentPowerCondition->defaultTimerSetting =
                    M_BytesTo4ByteValue(epcLog[offset + 7], epcLog[offset + 6], epcLog[offset + 5], epcLog[offset + 4]);
                currentPowerCondition->savedTimerSetting = M_BytesTo4ByteValue(epcLog[offset + 11], epcLog[offset + 10],
                                                                               epcLog[offset + 9], epcLog[offset + 8]);
                currentPowerCondition->currentTimerSetting = M_BytesTo4ByteValue(
                    epcLog[offset + 15], epcLog[offset + 14], epcLog[offset + 13], epcLog[offset + 12]);
                currentPowerCondition->nominalRecoveryTimeToActiveState = M_BytesTo4ByteValue(
                    epcLog[offset + 19], epcLog[offset + 18], epcLog[offset + 17], epcLog[offset + 16]);
                currentPowerCondition->minimumTimerSetting = M_BytesTo4ByteValue(
                    epcLog[offset + 23], epcLog[offset + 22], epcLog[offset + 21], epcLog[offset + 20]);
                currentPowerCondition->maximumTimerSetting = M_BytesTo4ByteValue(
                    epcLog[offset + 27], epcLog[offset + 26], epcLog[offset + 25], epcLog[offset + 24]);
            }
        }
    }
    safe_free_aligned(&epcLog);
    return ret;
}

static eReturnValues scsi_Get_EPC_Settings(const tDevice* device, ptrEpcSettings epcSettings)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (epcSettings == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    bool powerConditionVPDsupported = true;
    DECLARE_ZERO_INIT_ARRAY(uint8_t, epcVPDPage, VPD_POWER_CONDITION_LEN);
    if (SUCCESS == get_SCSI_VPD(device, POWER_CONDITION, M_NULLPTR, M_NULLPTR, true, epcVPDPage,
                                VPD_POWER_CONDITION_LEN, M_NULLPTR))
    {
        // NOTE: Recovery times are in milliseconds, not 100 milliseconds like other timers, so need to convert to 100
        // millisecond units for now-TJE idle a
        if (epcVPDPage[5] & BIT0)
        {
            epcSettings->idle_a.powerConditionSupported          = true;
            epcSettings->idle_a.nominalRecoveryTimeToActiveState = M_BytesTo2ByteValue(epcVPDPage[12], epcVPDPage[13]);
            if (epcSettings->idle_a.nominalRecoveryTimeToActiveState > 0)
            {
                epcSettings->idle_a.nominalRecoveryTimeToActiveState =
                    M_Max(1, epcSettings->idle_a.nominalRecoveryTimeToActiveState / 100);
            }
        }
        // idle b
        if (epcVPDPage[5] & BIT1)
        {
            epcSettings->idle_b.powerConditionSupported          = true;
            epcSettings->idle_b.nominalRecoveryTimeToActiveState = M_BytesTo2ByteValue(epcVPDPage[14], epcVPDPage[15]);
            if (epcSettings->idle_b.nominalRecoveryTimeToActiveState > 0)
            {
                epcSettings->idle_b.nominalRecoveryTimeToActiveState =
                    M_Max(1, epcSettings->idle_b.nominalRecoveryTimeToActiveState / 100);
            }
        }
        // idle c
        if (epcVPDPage[5] & BIT2)
        {
            epcSettings->idle_c.powerConditionSupported          = true;
            epcSettings->idle_c.nominalRecoveryTimeToActiveState = M_BytesTo2ByteValue(epcVPDPage[16], epcVPDPage[17]);
            if (epcSettings->idle_c.nominalRecoveryTimeToActiveState > 0)
            {
                epcSettings->idle_c.nominalRecoveryTimeToActiveState =
                    M_Max(1, epcSettings->idle_c.nominalRecoveryTimeToActiveState / 100);
            }
        }
        // standby z
        if (epcVPDPage[4] & BIT0)
        {
            epcSettings->standby_z.powerConditionSupported          = true;
            epcSettings->standby_z.nominalRecoveryTimeToActiveState = M_BytesTo2ByteValue(epcVPDPage[8], epcVPDPage[9]);
            if (epcSettings->standby_z.nominalRecoveryTimeToActiveState > 0)
            {
                epcSettings->standby_z.nominalRecoveryTimeToActiveState =
                    M_Max(1, epcSettings->standby_z.nominalRecoveryTimeToActiveState / 100);
            }
        }
        // standby y
        if (epcVPDPage[4] & BIT1)
        {
            epcSettings->standby_y.powerConditionSupported = true;
            epcSettings->standby_y.nominalRecoveryTimeToActiveState =
                M_BytesTo2ByteValue(epcVPDPage[10], epcVPDPage[11]);
            if (epcSettings->standby_y.nominalRecoveryTimeToActiveState > 0)
            {
                epcSettings->standby_y.nominalRecoveryTimeToActiveState =
                    M_Max(1, epcSettings->standby_y.nominalRecoveryTimeToActiveState / 100);
            }
        }
    }
    else
    {
        powerConditionVPDsupported = false;
    }
    epcSettings->settingsAffectMultipleLogicalUnits =
        scsi_Mode_Pages_Shared_By_Multiple_Logical_Units(device, MP_POWER_CONDTION, 0);
    // now time to read the mode pages for the other information (start with current, then saved, then default)
    DECLARE_ZERO_INIT_ARRAY(uint8_t, epcModePage, MP_POWER_CONDITION_LEN + MODE_PARAMETER_HEADER_10_LEN);
    for (eScsiModePageControl modePageControl = MPC_CURRENT_VALUES; modePageControl <= MPC_SAVED_VALUES;
         ++modePageControl)
    {
        safe_memset(epcModePage, MP_POWER_CONDITION_LEN + MODE_PARAMETER_HEADER_10_LEN, 0,
                    MP_POWER_CONDITION_LEN + MODE_PARAMETER_HEADER_10_LEN);
        bool    gotData      = false;
        uint8_t headerLength = MODE_PARAMETER_HEADER_10_LEN;
        if (SUCCESS == scsi_Mode_Sense_10(device, MP_POWER_CONDTION,
                                          MP_POWER_CONDITION_LEN + MODE_PARAMETER_HEADER_10_LEN, 0, true, false,
                                          modePageControl, epcModePage))
        {
            gotData = true;
        }
        else if (SUCCESS == scsi_Mode_Sense_6(device, MP_POWER_CONDTION,
                                              MP_POWER_CONDITION_LEN + MODE_PARAMETER_HEADER_6_LEN, 0, true,
                                              modePageControl, epcModePage))
        {
            gotData      = true;
            headerLength = MODE_PARAMETER_HEADER_6_LEN;
        }
        if (gotData)
        {
            bool*     idleAenabledBit      = M_NULLPTR;
            uint32_t* idleAtimerSetting    = M_NULLPTR;
            bool*     idleBenabledBit      = M_NULLPTR;
            uint32_t* idleBtimerSetting    = M_NULLPTR;
            bool*     idleCenabledBit      = M_NULLPTR;
            uint32_t* idleCtimerSetting    = M_NULLPTR;
            bool*     standbyYenabledBit   = M_NULLPTR;
            uint32_t* standbyYtimerSetting = M_NULLPTR;
            bool*     standbyZenabledBit   = M_NULLPTR;
            uint32_t* standbyZtimerSetting = M_NULLPTR;
            switch (modePageControl)
            {
            case MPC_CURRENT_VALUES:
                idleAenabledBit      = &epcSettings->idle_a.currentTimerEnabled;
                idleAtimerSetting    = &epcSettings->idle_a.currentTimerSetting;
                idleBenabledBit      = &epcSettings->idle_b.currentTimerEnabled;
                idleBtimerSetting    = &epcSettings->idle_b.currentTimerSetting;
                idleCenabledBit      = &epcSettings->idle_c.currentTimerEnabled;
                idleCtimerSetting    = &epcSettings->idle_c.currentTimerSetting;
                standbyYenabledBit   = &epcSettings->standby_y.currentTimerEnabled;
                standbyYtimerSetting = &epcSettings->standby_y.currentTimerSetting;
                standbyZenabledBit   = &epcSettings->standby_z.currentTimerEnabled;
                standbyZtimerSetting = &epcSettings->standby_z.currentTimerSetting;
                // special case. If reading the current values, check the page for the PS bit (parameters savable) to
                // note this for all power conditions.
                if (epcModePage[headerLength + 0] & BIT7)
                {
                    epcSettings->idle_a.powerConditionSaveable    = true;
                    epcSettings->idle_b.powerConditionSaveable    = true;
                    epcSettings->idle_c.powerConditionSaveable    = true;
                    epcSettings->standby_y.powerConditionSaveable = true;
                    epcSettings->standby_z.powerConditionSaveable = true;
                }
                break;
            case MPC_CHANGABLE_VALUES:
                idleAenabledBit      = &epcSettings->idle_a.powerConditionChangeable;
                idleAtimerSetting    = &epcSettings->idle_a.maximumTimerSetting;
                idleBenabledBit      = &epcSettings->idle_b.powerConditionChangeable;
                idleBtimerSetting    = &epcSettings->idle_b.maximumTimerSetting;
                idleCenabledBit      = &epcSettings->idle_c.powerConditionChangeable;
                idleCtimerSetting    = &epcSettings->idle_c.maximumTimerSetting;
                standbyYenabledBit   = &epcSettings->standby_y.powerConditionChangeable;
                standbyYtimerSetting = &epcSettings->standby_y.maximumTimerSetting;
                standbyZenabledBit   = &epcSettings->standby_z.powerConditionChangeable;
                standbyZtimerSetting = &epcSettings->standby_z.maximumTimerSetting;
                break;
            case MPC_DEFAULT_VALUES:
                idleAenabledBit      = &epcSettings->idle_a.defaultTimerEnabled;
                idleAtimerSetting    = &epcSettings->idle_a.defaultTimerSetting;
                idleBenabledBit      = &epcSettings->idle_b.defaultTimerEnabled;
                idleBtimerSetting    = &epcSettings->idle_b.defaultTimerSetting;
                idleCenabledBit      = &epcSettings->idle_c.defaultTimerEnabled;
                idleCtimerSetting    = &epcSettings->idle_c.defaultTimerSetting;
                standbyYenabledBit   = &epcSettings->standby_y.defaultTimerEnabled;
                standbyYtimerSetting = &epcSettings->standby_y.defaultTimerSetting;
                standbyZenabledBit   = &epcSettings->standby_z.defaultTimerEnabled;
                standbyZtimerSetting = &epcSettings->standby_z.defaultTimerSetting;
                break;
            case MPC_SAVED_VALUES:
                idleAenabledBit      = &epcSettings->idle_a.savedTimerEnabled;
                idleAtimerSetting    = &epcSettings->idle_a.savedTimerSetting;
                idleBenabledBit      = &epcSettings->idle_b.savedTimerEnabled;
                idleBtimerSetting    = &epcSettings->idle_b.savedTimerSetting;
                idleCenabledBit      = &epcSettings->idle_c.savedTimerEnabled;
                idleCtimerSetting    = &epcSettings->idle_c.savedTimerSetting;
                standbyYenabledBit   = &epcSettings->standby_y.savedTimerEnabled;
                standbyYtimerSetting = &epcSettings->standby_y.savedTimerSetting;
                standbyZenabledBit   = &epcSettings->standby_z.savedTimerEnabled;
                standbyZtimerSetting = &epcSettings->standby_z.savedTimerSetting;
                break;
            default:
                continue;
            }
            // idle a
            if (epcModePage[headerLength + 3] & BIT1)
            {
                *idleAenabledBit = true;
                if (powerConditionVPDsupported == false)
                {
                    if (modePageControl == MPC_CHANGABLE_VALUES || modePageControl == MPC_CURRENT_VALUES)
                    {
                        // special case, mostly for older drives before EPC.
                        // SCSI has supported this mode page for a while, so if it's the "changeable" page set
                        // "supported" bits for different power conditions.
                        epcSettings->idle_a.powerConditionSupported = true;
                    }
                }
            }
            *idleAtimerSetting = M_BytesTo4ByteValue(epcModePage[headerLength + 4], epcModePage[headerLength + 5],
                                                     epcModePage[headerLength + 6], epcModePage[headerLength + 7]);
            // standby z
            if (epcModePage[headerLength + 3] & BIT0)
            {
                *standbyZenabledBit = true;
                if (modePageControl == MPC_CHANGABLE_VALUES || modePageControl == MPC_CURRENT_VALUES)
                {
                    // special case, mostly for older drives before EPC.
                    // SCSI has supported this mode page for a while, so if it's the "changeable" page set "supported"
                    // bits for different power conditions.
                    epcSettings->standby_z.powerConditionSupported = true;
                }
            }
            *standbyZtimerSetting = M_BytesTo4ByteValue(epcModePage[headerLength + 8], epcModePage[headerLength + 9],
                                                        epcModePage[headerLength + 10], epcModePage[headerLength + 11]);
            if (epcModePage[headerLength + 1] >
                0x0A) // before EPC this page was shorter, so do not try to access the rest of it as the data is invalid
            {
                // idle b
                if (epcModePage[headerLength + 3] & BIT2)
                {
                    *idleBenabledBit = true;
                }
                *idleBtimerSetting =
                    M_BytesTo4ByteValue(epcModePage[headerLength + 12], epcModePage[headerLength + 13],
                                        epcModePage[headerLength + 14], epcModePage[headerLength + 15]);
                // idle c
                if (epcModePage[headerLength + 3] & BIT3)
                {
                    *idleCenabledBit = true;
                }
                *idleCtimerSetting =
                    M_BytesTo4ByteValue(epcModePage[headerLength + 16], epcModePage[headerLength + 17],
                                        epcModePage[headerLength + 18], epcModePage[headerLength + 19]);
                // standby y
                if (epcModePage[headerLength + 2] & BIT0)
                {
                    *standbyYenabledBit = true;
                }
                *standbyYtimerSetting =
                    M_BytesTo4ByteValue(epcModePage[headerLength + 20], epcModePage[headerLength + 21],
                                        epcModePage[headerLength + 22], epcModePage[headerLength + 23]);
            }
            ret = SUCCESS;
        }
    }
    return ret;
}

eReturnValues get_EPC_Settings(const tDevice* device, ptrEpcSettings epcSettings)
{
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        return ata_Get_EPC_Settings(device, epcSettings);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        return scsi_Get_EPC_Settings(device, epcSettings);
    }
    else
    {
        return NOT_SUPPORTED;
    }
}

static void print_Power_Condition(ptrPowerConditionInfo condition, const char* conditionName)
{
    printf("%-9s ", conditionName);
    if (condition->currentTimerEnabled)
    {
        print_str("*");
    }
    else
    {
        print_str(" ");
    }
    printf("%-12" PRIu32 " ", condition->currentTimerSetting);
    if (condition->defaultTimerEnabled)
    {
        print_str("*");
    }
    else
    {
        print_str(" ");
    }
    printf("%-12" PRIu32 " ", condition->defaultTimerSetting);
    if (condition->savedTimerEnabled)
    {
        print_str("*");
    }
    else
    {
        print_str(" ");
    }
    printf("%-10" PRIu32 " ", condition->savedTimerSetting);
    printf("%-13" PRIu32 " ", condition->nominalRecoveryTimeToActiveState);
    printf("%-11" PRIu32 " ", condition->minimumTimerSetting);
    printf("%-10" PRIu32 " ", condition->maximumTimerSetting);
    if (condition->powerConditionChangeable)
    {
        print_str(" Y");
    }
    else
    {
        print_str(" N");
    }
    if (condition->powerConditionSaveable)
    {
        print_str(" Y");
    }
    else
    {
        print_str(" N");
    }
    print_str("\n");
}

void print_EPC_Settings(const tDevice* device, ptrEpcSettings epcSettings)
{

    if (epcSettings == M_NULLPTR)
    {
        return;
    }

    M_USE_UNUSED(device);
    print_str("\n===EPC Settings===\n");
    print_str("\t* = timer is enabled\n");
    print_str("\tC column = Changeable\n");
    print_str("\tS column = Savable\n");
    print_str("\tAll times are in 100 milliseconds\n\n");
    printf("%-9s %-13s %-13s %-11s %-13s %-11s %-11s C S\n", "Name", "Current Timer", "Default Timer", "Saved Timer",
           "Recovery Time", "Min Timer", "Max Timer");
    if (epcSettings->idle_a.powerConditionSupported)
    {
        print_Power_Condition(&epcSettings->idle_a, "Idle A");
    }
    if (epcSettings->idle_b.powerConditionSupported)
    {
        print_Power_Condition(&epcSettings->idle_b, "Idle B");
    }
    if (epcSettings->idle_c.powerConditionSupported)
    {
        print_Power_Condition(&epcSettings->idle_c, "Idle C");
    }
    if (epcSettings->standby_y.powerConditionSupported)
    {
        print_Power_Condition(&epcSettings->standby_y, "Standby Y");
    }
    if (epcSettings->standby_z.powerConditionSupported)
    {
        print_Power_Condition(&epcSettings->standby_z, "Standby Z");
    }
    /*if (epcSettings->settingsAffectMultipleLogicalUnits)
    {
        print_str("\nNote: All settings affect multiple logical units.\n");
    }*/
}

// NOTE: This is intended for legacy drives that don't support extra timers for EPC. An EPC drive will still process
// this command though!!! - TJE More Notes: This function is similar to the EPC function, BUT this will not check for
// the VPD page as it doesn't exist on old drives.
//             These functions should probaby be combined at some point

eReturnValues scsi_Set_Legacy_Power_Conditions(const tDevice*            device,
                                               bool                      restoreAllToDefaults,
                                               ptrPowerConditionSettings standbyTimer,
                                               ptrPowerConditionSettings idleTimer,
                                               bool                      saveChanges)
{
    // Check the changable page for support of idle and standby timers before beginning???
    if (!restoreAllToDefaults || standbyTimer == M_NULLPTR || idleTimer == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }
    powerConditionTimers pwrConditions;
    safe_memset(&pwrConditions, sizeof(powerConditionTimers), 0, sizeof(powerConditionTimers));
    if (standbyTimer != M_NULLPTR)
    {
        safe_memcpy(&pwrConditions.standby, sizeof(powerConditionSettings), standbyTimer,
                    sizeof(powerConditionSettings));
    }
    if (idleTimer != M_NULLPTR)
    {
        safe_memcpy(&pwrConditions.idle, sizeof(powerConditionSettings), idleTimer, sizeof(powerConditionSettings));
    }
    return scsi_Set_Power_Conditions(device, restoreAllToDefaults, &pwrConditions, saveChanges);
}

// using 100 millisecond increments since that is what SCSI uses and the methodology in here will match SAT spec. This
// seemed simpler - TJE
static eReturnValues ata_Set_Standby_Timer(const tDevice* device, uint32_t hundredMillisecondIncrements)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word049)) &&
        le16_to_host(device->drive_info.IdentifyData.ata.Word049) &
            BIT13) // this is the only bit across all ATA standards that will most likely work. Prior to ATA3, there was
                   // no other support bit for the power management feature set.
    {
        uint8_t standbyTimer     = UINT8_C(0);
        uint8_t currentPowerMode = UINT8_C(0);
        if (hundredMillisecondIncrements == 0)
        {
            // send standby immediate and return immediately
            return ata_Standby_Immediate(device);
        }
        else if (hundredMillisecondIncrements >= UINT32_C(1) && hundredMillisecondIncrements <= UINT32_C(12000))
        {
            standbyTimer = C_CAST(uint8_t, ((hundredMillisecondIncrements - UINT32_C(1)) / UINT32_C(50)) + UINT32_C(1));
        }
        else if (hundredMillisecondIncrements >= UINT32_C(12001) && hundredMillisecondIncrements <= UINT32_C(12600))
        {
            standbyTimer = 0xFC;
        }
        else if (hundredMillisecondIncrements >= UINT32_C(12601) && hundredMillisecondIncrements <= UINT32_C(12750))
        {
            standbyTimer = 0xFF;
        }
        else if (hundredMillisecondIncrements >= UINT32_C(12751) && hundredMillisecondIncrements <= UINT32_C(17999))
        {
            standbyTimer = 0xF1;
        }
        else if (hundredMillisecondIncrements >= UINT32_C(18000) && hundredMillisecondIncrements <= UINT32_C(198000))
        {
            standbyTimer = C_CAST(uint8_t, (hundredMillisecondIncrements / UINT32_C(18000)) + UINT32_C(240));
        }
        else
        {
            standbyTimer = 0xFD;
        }
        // if we made it here, set the timer.
        // Check the current power mode. If the drive is in standby already, use standby, otherwise use idle to set the
        // timer. NOTE: Previously only the standby command was used like in SAT-5 for non-EPC. Changed to checking
        // states, but only non-EPC behavior
        ata_Check_Power_Mode(device, &currentPowerMode);
        if (currentPowerMode == 0)
        {
            ret = ata_Standby(device, standbyTimer);
        }
        else
        {
            // not in standby mode, so use idle. NOTE: This does not take into account EPC, but this is meant for legacy
            // drives anyways, so this does not need to be complicated. This may affect standby_y, but users should be
            // using EPC instead if they want better, more granular timers anyways. -TJE
            ret = ata_Idle(device, standbyTimer);
        }
    }
    return ret;
}

eReturnValues scsi_Set_Standby_Timer_State(const tDevice* device, bool enable, bool saveChanges)
{
    powerConditionSettings standbyTimer;
    safe_memset(&standbyTimer, sizeof(powerConditionSettings), 0, sizeof(powerConditionSettings));
    standbyTimer.powerConditionValid = true;
    standbyTimer.enableValid         = true;
    standbyTimer.enable              = enable;

    return scsi_Set_Legacy_Power_Conditions(device, false, &standbyTimer, M_NULLPTR, saveChanges);
}

eReturnValues set_Standby_Timer(const tDevice* device,
                                uint32_t       hundredMillisecondIncrements,
                                bool           restoreToDefault,
                                bool           saveChanges)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        if (restoreToDefault)
        {
            // Cannot restore ATA to default values. This is done with a power cycle or some SATL that has a capability
            // to remember the original timer (in which case, the scsi function should be used instead)
            return NOT_SUPPORTED;
        }
        ret = ata_Set_Standby_Timer(device, hundredMillisecondIncrements);
    }
    else if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        powerConditionSettings standbyTimer;
        safe_memset(&standbyTimer, sizeof(powerConditionSettings), 0, sizeof(powerConditionSettings));
        standbyTimer.powerConditionValid = true;
        if (restoreToDefault)
        {
            standbyTimer.restoreToDefault = restoreToDefault;
        }
        else
        {
            standbyTimer.enableValid                         = true;
            standbyTimer.enable                              = true;
            standbyTimer.timerValid                          = true;
            standbyTimer.timerInHundredMillisecondIncrements = hundredMillisecondIncrements;
        }
        ret = scsi_Set_Legacy_Power_Conditions(device, false, &standbyTimer, M_NULLPTR, saveChanges);
    }
    return ret;
}

eReturnValues scsi_Set_Idle_Timer_State(const tDevice* device, bool enable, bool saveChanges)
{
    powerConditionSettings idleTimer;
    safe_memset(&idleTimer, sizeof(powerConditionSettings), 0, sizeof(powerConditionSettings));
    idleTimer.powerConditionValid = true;
    idleTimer.enableValid         = true;
    idleTimer.enable              = enable;

    return scsi_Set_Legacy_Power_Conditions(device, false, M_NULLPTR, &idleTimer, saveChanges);
}

eReturnValues set_Idle_Timer(const tDevice* device,
                             uint32_t       hundredMillisecondIncrements,
                             bool           restoreToDefault,
                             bool           saveChanges)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == SCSI_DRIVE)
    {
        powerConditionSettings idleTimer;
        safe_memset(&idleTimer, sizeof(powerConditionSettings), 0, sizeof(powerConditionSettings));
        idleTimer.powerConditionValid = true;
        if (restoreToDefault)
        {
            idleTimer.restoreToDefault = restoreToDefault;
        }
        else
        {
            idleTimer.enableValid                         = true;
            idleTimer.enable                              = true;
            idleTimer.timerValid                          = true;
            idleTimer.timerInHundredMillisecondIncrements = hundredMillisecondIncrements;
        }
        ret = scsi_Set_Legacy_Power_Conditions(device, false, M_NULLPTR, &idleTimer, saveChanges);
    }
    return ret;
}

eReturnValues sata_Get_Device_Initiated_Interface_Power_State_Transitions(const tDevice* device,
                                                                          bool*          supported,
                                                                          bool*          enabled)
{
    eReturnValues ret = NOT_SUPPORTED;
    if ((device->drive_info.drive_type == ATA_DRIVE || device->drive_info.drive_type == ATAPI_DRIVE) && is_SATA(device))
    {
        ret = SUCCESS;

        if (supported != M_NULLPTR)
        {
            if (is_ATA_Identify_Word_Valid_SATA(le16_to_host(device->drive_info.IdentifyData.ata.Word078)) &&
                le16_to_host(device->drive_info.IdentifyData.ata.Word078) & BIT3)
            {
                *supported = true;
            }
            else
            {
                *supported = false;
            }
        }
        if (enabled != M_NULLPTR)
        {
            if (is_ATA_Identify_Word_Valid_SATA(le16_to_host(device->drive_info.IdentifyData.ata.Word079)) &&
                le16_to_host(device->drive_info.IdentifyData.ata.Word079) & BIT3)
            {
                *enabled = true;
            }
            else
            {
                *enabled = false;
            }
        }
    }
    return ret;
}

eReturnValues sata_Set_Device_Initiated_Interface_Power_State_Transitions(const tDevice* device, bool enable)
{
    eReturnValues ret = NOT_SUPPORTED;
    if ((device->drive_info.drive_type == ATA_DRIVE || device->drive_info.drive_type == ATAPI_DRIVE) && is_SATA(device))
    {
        bool supported = false;
        if (SUCCESS == sata_Get_Device_Initiated_Interface_Power_State_Transitions(device, &supported, M_NULLPTR))
        {
            DECLARE_ZERO_INIT_ARRAY(uint8_t, iddata, LEGACY_DRIVE_SEC_SIZE);
            if (enable)
            {
                ret = ata_SF_SATA_Dev_Initiated_Power_State_Transitions(device, ATA_SF_ENABLE);
            }
            else
            {
                ret = ata_SF_SATA_Dev_Initiated_Power_State_Transitions(device, ATA_SF_DISABLE);
            }
            // Issue an identify to update the identify data...
            if (device->drive_info.drive_type == ATA_DRIVE)
            {
                ata_Identify(device, iddata, LEGACY_DRIVE_SEC_SIZE);
            }
            else if (device->drive_info.drive_type == ATAPI_DRIVE)
            {
                ata_Identify_Packet_Device(device, iddata, LEGACY_DRIVE_SEC_SIZE);
            }
        }
    }
    return ret;
}

eReturnValues sata_Get_Device_Automatic_Partial_To_Slumber_Transtisions(const tDevice* device,
                                                                        bool*          supported,
                                                                        bool*          enabled)
{
    eReturnValues ret = NOT_SUPPORTED;
    if ((device->drive_info.drive_type == ATA_DRIVE || device->drive_info.drive_type == ATAPI_DRIVE) && is_SATA(device))
    {
        ret = SUCCESS;

        if (supported != M_NULLPTR)
        {
            if (is_ATA_Identify_Word_Valid_SATA(le16_to_host(device->drive_info.IdentifyData.ata.Word076)) &&
                le16_to_host(device->drive_info.IdentifyData.ata.Word076) & BIT14)
            {
                *supported = true;
            }
            else
            {
                *supported = false;
            }
        }
        if (enabled != M_NULLPTR)
        {
            if (is_ATA_Identify_Word_Valid_SATA(le16_to_host(device->drive_info.IdentifyData.ata.Word079)) &&
                le16_to_host(device->drive_info.IdentifyData.ata.Word079) & BIT7)
            {
                *enabled = true;
            }
            else
            {
                *enabled = false;
            }
        }
    }
    return ret;
}

eReturnValues sata_Set_Device_Automatic_Partial_To_Slumber_Transtisions(const tDevice* device, bool enable)
{
    eReturnValues ret = NOT_SUPPORTED;
    if ((device->drive_info.drive_type == ATA_DRIVE || device->drive_info.drive_type == ATAPI_DRIVE) && is_SATA(device))
    {
        bool          dipmSupported = false;
        bool          dipmEnabled   = false;
        eReturnValues getDIPM       = sata_Get_Device_Initiated_Interface_Power_State_Transitions(
            device, &dipmSupported, &dipmEnabled); // DIPM must be ENABLED before we can change this feature!!
        if (getDIPM == SUCCESS && dipmSupported && dipmEnabled)
        {
            bool supported = false;
            if (SUCCESS == sata_Get_Device_Automatic_Partial_To_Slumber_Transtisions(device, &supported, M_NULLPTR))
            {
                DECLARE_ZERO_INIT_ARRAY(uint8_t, iddata, LEGACY_DRIVE_SEC_SIZE);
                if (enable)
                {
                    ret = ata_SF_SATA_Dev_Auto_Partial_To_Slumber_Transitions(device, ATA_SF_ENABLE);
                }
                else
                {
                    ret = ata_SF_SATA_Dev_Auto_Partial_To_Slumber_Transitions(device, ATA_SF_DISABLE);
                }
                // Issue an identify to update the identify data...
                if (device->drive_info.drive_type == ATA_DRIVE)
                {
                    ata_Identify(device, iddata, LEGACY_DRIVE_SEC_SIZE);
                }
                else if (device->drive_info.drive_type == ATAPI_DRIVE)
                {
                    ata_Identify_Packet_Device(device, iddata, LEGACY_DRIVE_SEC_SIZE);
                }
            }
        }
    }
    return ret;
}

eReturnValues transition_To_Active(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE && device->drive_info.interface_type == IDE_INTERFACE)
    {
        // no ATA command to do this, so we need to issue something to perform a medium access.
        uint64_t randomLBA = UINT64_C(0);
        seed_64(C_CAST(uint64_t, time(M_NULLPTR)));
        randomLBA = random_Range_64(0, device->drive_info.deviceMaxLba);
        ret       = ata_Read_Verify(device, randomLBA, 1);
    }
    else // treat as SCSI
    {
        if (device->drive_info.scsiVersion >
            SCSI_VERSION_SCSI2) // checking for support after SCSI2. This isn't perfect, but should be ok for now.
        {
            ret = scsi_Start_Stop_Unit(device, false, 0, PC_ACTIVE, false, false, false);
        }
        else
        {
            // before you could specify a power condition, you used the "Start" bit as a way to move from standby to
            // active
            ret = scsi_Start_Stop_Unit(device, false, 0, PC_START_VALID, false, false, true);
        }
    }
    return ret;
}

eReturnValues transition_To_Standby(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Standby_Immediate(device);
    }
    else // treat as SCSI
    {
        if (device->drive_info.scsiVersion >
            SCSI_VERSION_SCSI2) // checking for support after SCSI2. This isn't perfect, but should be ok for now.
        {
            ret = scsi_Start_Stop_Unit(device, false, 0, PC_STANDBY, false, false, false);
        }
        else
        {
            // before you could specify a power condition, you used the "Start" bit as a way to move from standby to
            // active
            ret = scsi_Start_Stop_Unit(device, false, 0, 0, false, false, false);
        }
    }
    return ret;
}

eReturnValues transition_To_Idle(const tDevice* device, bool unload)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        if (unload)
        {
            if ((is_ATA_Identify_Word_Valid_With_Bits_14_And_15(
                     le16_to_host(device->drive_info.IdentifyData.ata.Word084)) &&
                 le16_to_host(device->drive_info.IdentifyData.ata.Word084) & BIT13) ||
                (is_ATA_Identify_Word_Valid_With_Bits_14_And_15(
                     le16_to_host(device->drive_info.IdentifyData.ata.Word087)) &&
                 le16_to_host(device->drive_info.IdentifyData.ata.Word087) & BIT13))
            {
                // send the command since it supports the unload feature...otherwise we return NOT_SUPPORTED
                ret = ata_Idle_Immediate(device, true);
            }
        }
        else
        {
            ret = ata_Idle_Immediate(device, false);
        }
    }
    else
    {
        if (device->drive_info.scsiVersion >
            SCSI_VERSION_SCSI2) // checking for support after SCSI2. This isn't perfect, but should be ok for now.
        {
            if (unload)
            {
                // unload can happen if power condition modifier set to 1. Needs SBC3/SPC3.
                if (device->drive_info.scsiVersion > SCSI_VERSION_SPC_2)
                {
                    ret = scsi_Start_Stop_Unit(device, false, 1, PC_IDLE, false, false, false);
                }
            }
            else
            {
                ret = scsi_Start_Stop_Unit(device, false, 0, PC_IDLE, false, false, false);
            }
        }
    }
    return ret;
}

eReturnValues transition_To_Sleep(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = ata_Sleep(device);
    }
    else // treat as SCSI
    {
        if (device->drive_info.scsiVersion >
            SCSI_VERSION_SCSI2) // checking for support after SCSI2. This isn't perfect, but should be ok for now.
        {
            ret = scsi_Start_Stop_Unit(device, false, 0, PC_SLEEP, false, false,
                                       false); // This is obsolete since SBC2...but we'll send it anyways
        }
    }
    return ret;
}

eReturnValues scsi_Set_Partial_Slumber(const tDevice* device,
                                       bool           enablePartial,
                                       bool           enableSlumber,
                                       bool           partialValid,
                                       bool           slumberValid,
                                       bool           allPhys,
                                       uint8_t        phyNumber)
{
    eReturnValues ret = SUCCESS;
    if (!partialValid && !slumberValid)
    {
        return BAD_PARAMETER;
    }
    bool     gotFullPageLength  = false;
    bool     alreadyHaveAllData = false;
    uint16_t enhPhyControlLength =
        MODE_PARAMETER_HEADER_10_LEN + 8 + 40; // first 8 bytes are a "header" followed by 20 bytes per phy and setting
                                               // this for 2 phys since that is most common right now. -TJE
    uint8_t* enhSasPhyControl =
        M_REINTERPRET_CAST(uint8_t*, safe_calloc_aligned(enhPhyControlLength * sizeof(uint8_t), sizeof(uint8_t),
                                                         device->os_info.minimumAlignment));
    if (enhSasPhyControl == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }
    // read first 4 bytes to get total mode page length, then re-read the part with all the data
    if (SUCCESS == (ret = scsi_Mode_Sense_10(device, MP_PROTOCOL_SPECIFIC_PORT, enhPhyControlLength, 0x03, true, false,
                                             MPC_CURRENT_VALUES, enhSasPhyControl)))
    {
        if (enhPhyControlLength < M_BytesTo2ByteValue(enhSasPhyControl[0], enhSasPhyControl[1]) +
                                      MODE_PARAMETER_HEADER_10_LEN +
                                      M_BytesTo2ByteValue(enhSasPhyControl[6], enhSasPhyControl[7]))
        {
            // parse the header to figure out full page length
            enhPhyControlLength = C_CAST(uint16_t, M_BytesTo2ByteValue(enhSasPhyControl[0], enhSasPhyControl[1]) +
                                                       MODE_PARAMETER_HEADER_10_LEN +
                                                       M_BytesTo2ByteValue(enhSasPhyControl[6], enhSasPhyControl[7]));
            gotFullPageLength   = true;
            uint8_t* temp       = safe_reallocf_aligned(C_CAST(void**, &enhSasPhyControl), 0, enhPhyControlLength,
                                                        device->os_info.minimumAlignment);
            if (temp == M_NULLPTR)
            {
                return MEMORY_FAILURE;
            }
            enhSasPhyControl = temp;
        }
        else
        {
            gotFullPageLength  = true;
            alreadyHaveAllData = true;
        }
    }
    if (gotFullPageLength)
    {
        if (alreadyHaveAllData ||
            SUCCESS == scsi_Mode_Sense_10(device, MP_PROTOCOL_SPECIFIC_PORT, enhPhyControlLength, 0x03, true, false,
                                          MPC_CURRENT_VALUES, enhSasPhyControl))
        {
            // make sure we got the header as we expect it, then validate we got all the data we needed.
            // uint16_t modeDataLength = M_BytesTo2ByteValue(enhSasPhyControl[0], enhSasPhyControl[1]);
            uint16_t blockDescriptorLength = M_BytesTo2ByteValue(enhSasPhyControl[MODE_HEADER_10_BLK_DESC_OFFSET],
                                                                 enhSasPhyControl[MODE_HEADER_10_BLK_DESC_OFFSET + 1]);
            // validate we got the right page
            if ((enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 0] & 0x3F) == 0x19 &&
                (enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 1]) == 0x03 &&
                (enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 0] & BIT6) > 0)
            {
                if ((enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 5] & 0x0F) ==
                    6) // make sure it's the SAS protocol page
                {
                    uint8_t  numberOfPhys = enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 7];
                    uint32_t phyDescriptorOffset = MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength +
                                                   8; // this will be set to the beginnging of the phy descriptors so
                                                      // that when looping through them, it is easier code to read.
                    uint16_t descriptorLength = UINT16_C(19);
                    for (uint16_t phyIter = UINT16_C(0);
                         phyIter < C_CAST(uint16_t, numberOfPhys) && phyDescriptorOffset < enhPhyControlLength;
                         ++phyIter, phyDescriptorOffset += descriptorLength)
                    {
                        uint8_t phyIdentifier = enhSasPhyControl[phyDescriptorOffset + 1];
                        descriptorLength      = M_BytesTo2ByteValue(enhSasPhyControl[phyDescriptorOffset + 2],
                                                                    enhSasPhyControl[phyDescriptorOffset + 3]);
                        // check if the caller requested changing all phys or a specific phy and only modify it's
                        // descriptor if either of those are true.
                        if (allPhys || phyNumber == phyIdentifier)
                        {
                            if (partialValid)
                            {
                                // byte 19, bit 1
                                if (enablePartial)
                                {
                                    M_SET_BIT8(enhSasPhyControl[phyDescriptorOffset + 19], 1);
                                }
                                else
                                {
                                    M_CLEAR_BIT8(enhSasPhyControl[phyDescriptorOffset + 19], 1);
                                }
                            }
                            if (slumberValid)
                            {
                                // byte 19, bit 2
                                if (enableSlumber)
                                {
                                    M_SET_BIT8(enhSasPhyControl[phyDescriptorOffset + 19], 2);
                                }
                                else
                                {
                                    M_CLEAR_BIT8(enhSasPhyControl[phyDescriptorOffset + 19], 2);
                                }
                            }
                        }
                    }
                    // we've finished making our changes to the mode page, so it's time to write it back!
                    if (SUCCESS != scsi_Mode_Select_10(device, enhPhyControlLength, true, true, false, enhSasPhyControl,
                                                       enhPhyControlLength))
                    {
                        ret = FAILURE;
                    }
                }
                else
                {
                    ret = NOT_SUPPORTED;
                }
            }
            else
            {
                ret = FAILURE;
            }
        }
        else
        {
            ret = FAILURE;
        }
    }
    safe_free_aligned(&enhSasPhyControl);
    return ret;
}

eReturnValues get_SAS_Enhanced_Phy_Control_Number_Of_Phys(const tDevice* device, uint8_t* phyCount)
{
    eReturnValues ret = SUCCESS;

    if (phyCount == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    uint16_t enhPhyControlLength = UINT16_C(8); // only need 8 bytes to get the number of phys
    uint8_t* enhSasPhyControl    = M_REINTERPRET_CAST(
        uint8_t*, safe_calloc_aligned((MODE_PARAMETER_HEADER_10_LEN + enhPhyControlLength) * sizeof(uint8_t),
                                         sizeof(uint8_t), device->os_info.minimumAlignment));
    if (enhSasPhyControl == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }
    // read first 4 bytes to get total mode page length, then re-read the part with all the data
    if (SUCCESS == (ret = scsi_Mode_Sense_10(device, MP_PROTOCOL_SPECIFIC_PORT,
                                             (MODE_PARAMETER_HEADER_10_LEN + enhPhyControlLength), 0x03, true, false,
                                             MPC_CURRENT_VALUES, enhSasPhyControl)))
    {
        // uint16_t modeDataLength = M_BytesTo2ByteValue(enhSasPhyControl[0], enhSasPhyControl[1]);
        uint16_t blockDescriptorLength = M_BytesTo2ByteValue(enhSasPhyControl[MODE_HEADER_10_BLK_DESC_OFFSET],
                                                             enhSasPhyControl[MODE_HEADER_10_BLK_DESC_OFFSET + 1]);
        // validate we got the right page
        if ((enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 0] & 0x3F) == 0x19 &&
            (enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 1]) == 0x03 &&
            (enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 0] & BIT6) > 0)
        {
            if ((enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 5] & 0x0F) ==
                6) // make sure it's the SAS protocol page
            {
                *phyCount = enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 7];
            }
        }
    }
    safe_free_aligned(&enhSasPhyControl);
    return ret;
}

eReturnValues get_SAS_Enhanced_Phy_Control_Partial_Slumber_Settings(const tDevice*      device,
                                                                    bool                allPhys,
                                                                    uint8_t             phyNumber,
                                                                    ptrSasEnhPhyControl enhPhyControlData,
                                                                    uint32_t            enhPhyControlDataSize)
{
    eReturnValues ret = SUCCESS;
    // make sure the structure that will be filled in makes sense at a quick check

    if (enhPhyControlData == M_NULLPTR || enhPhyControlDataSize == 0 ||
        enhPhyControlDataSize % sizeof(sasEnhPhyControl))
    {
        return BAD_PARAMETER;
    }

    bool     gotFullPageLength   = false;
    uint16_t enhPhyControlLength = UINT16_C(0);
    uint8_t* enhSasPhyControl    = M_REINTERPRET_CAST(
        uint8_t*, safe_calloc_aligned((MODE_PARAMETER_HEADER_10_LEN + enhPhyControlLength) * sizeof(uint8_t),
                                         sizeof(uint8_t), device->os_info.minimumAlignment));
    if (enhSasPhyControl == M_NULLPTR)
    {
        return MEMORY_FAILURE;
    }
    // read first 4 bytes to get total mode page length, then re-read the part with all the data
    if (SUCCESS == (ret = scsi_Mode_Sense_10(device, MP_PROTOCOL_SPECIFIC_PORT,
                                             (MODE_PARAMETER_HEADER_10_LEN + enhPhyControlLength), 0x03, true, false,
                                             MPC_CURRENT_VALUES, enhSasPhyControl)))
    {
        // parse the header to figure out full page length
        enhPhyControlLength = M_BytesTo2ByteValue(enhSasPhyControl[0], enhSasPhyControl[1]);
        gotFullPageLength   = true;
        safe_free_aligned(&enhSasPhyControl);
        enhSasPhyControl = M_REINTERPRET_CAST(
            uint8_t*, safe_calloc_aligned((MODE_PARAMETER_HEADER_10_LEN + enhPhyControlLength) * sizeof(uint8_t),
                                          sizeof(uint8_t), device->os_info.minimumAlignment));
        if (enhSasPhyControl == M_NULLPTR)
        {
            return MEMORY_FAILURE;
        }
    }
    if (gotFullPageLength)
    {
        if (SUCCESS == scsi_Mode_Sense_10(device, MP_PROTOCOL_SPECIFIC_PORT,
                                          (MODE_PARAMETER_HEADER_10_LEN + enhPhyControlLength), 0x03, true, false,
                                          MPC_CURRENT_VALUES, enhSasPhyControl))
        {
            // make sure we got the header as we expect it, then validate we got all the data we needed.
            // uint16_t modeDataLength = M_BytesTo2ByteValue(enhSasPhyControl[0], enhSasPhyControl[1]);
            uint16_t blockDescriptorLength = M_BytesTo2ByteValue(enhSasPhyControl[MODE_HEADER_10_BLK_DESC_OFFSET],
                                                                 enhSasPhyControl[MODE_HEADER_10_BLK_DESC_OFFSET + 1]);
            // validate we got the right page
            if ((enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 0] & 0x3F) == 0x19 &&
                (enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 1]) == 0x03 &&
                (enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 0] & BIT6) > 0)
            {
                if ((enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 5] & 0x0F) ==
                    6) // make sure it's the SAS protocol page
                {
                    uint8_t  numberOfPhys = enhSasPhyControl[MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength + 7];
                    uint32_t phyDescriptorOffset = MODE_PARAMETER_HEADER_10_LEN + blockDescriptorLength +
                                                   8; // this will be set to the beginnging of the phy descriptors so
                                                      // that when looping through them, it is easier code to read.
                    uint16_t descriptorLength = UINT16_C(19);
                    uint8_t  phyCounter       = UINT8_C(0);
                    for (uint16_t phyIter = UINT16_C(0);
                         phyIter < C_CAST(uint16_t, numberOfPhys) &&
                         (phyCounter * sizeof(sasEnhPhyControl)) < enhPhyControlDataSize;
                         ++phyIter, phyDescriptorOffset += descriptorLength, ++phyCounter)
                    {
                        uint8_t phyIdentifier = enhSasPhyControl[phyDescriptorOffset + 1];
                        descriptorLength      = M_BytesTo2ByteValue(enhSasPhyControl[phyDescriptorOffset + 2],
                                                                    enhSasPhyControl[phyDescriptorOffset + 3]) +
                                           4;
                        // check if the caller requested changing all phys or a specific phy and only modify it's
                        // descriptor if either of those are true.
                        if (allPhys)
                        {
                            enhPhyControlData[phyIdentifier].phyIdentifier = phyIdentifier;
                            enhPhyControlData[phyIdentifier].enablePartial =
                                enhSasPhyControl[phyDescriptorOffset + 19] & BIT1;
                            enhPhyControlData[phyIdentifier].enableSlumber =
                                enhSasPhyControl[phyDescriptorOffset + 19] & BIT2;
                        }
                        else if (phyNumber == phyIdentifier)
                        {
                            enhPhyControlData->phyIdentifier = phyIdentifier;
                            enhPhyControlData->enablePartial = enhSasPhyControl[phyDescriptorOffset + 19] & BIT1;
                            enhPhyControlData->enableSlumber = enhSasPhyControl[phyDescriptorOffset + 19] & BIT2;
                        }
                    }
                }
                else
                {
                    ret = NOT_SUPPORTED;
                }
            }
            else
            {
                ret = FAILURE;
            }
        }
        else
        {
            ret = FAILURE;
        }
    }
    safe_free_aligned(&enhSasPhyControl);

    return ret;
}

void show_SAS_Enh_Phy_Control_Partial_Slumber(ptrSasEnhPhyControl enhPhyControlData,
                                              uint32_t            enhPhyControlDataSize,
                                              bool                showPartial,
                                              bool                showSlumber)
{
    if (!showPartial && !showSlumber)
    {
        return; // nothing that matters was requested to be shown
    }

    if (enhPhyControlData == M_NULLPTR || enhPhyControlDataSize == UINT32_C(0) ||
        enhPhyControlDataSize % sizeof(sasEnhPhyControl))
    {
        return; // bad parameter that could cause breakage
    }

    uint32_t totalPhys = enhPhyControlDataSize / sizeof(sasEnhPhyControl);
    // Print a format header
    print_str("Phy#");
    if (showPartial)
    {
        print_str("\tPartial ");
    }
    if (showSlumber)
    {
        print_str("\tSlumber");
    }
    print_str("\n");
    for (uint32_t phyIter = UINT32_C(0); phyIter < totalPhys; ++phyIter)
    {
        printf(" %2" PRIu8 " ", enhPhyControlData[phyIter].phyIdentifier);
        if (showPartial)
        {
            if (enhPhyControlData[phyIter].enablePartial)
            {
                print_str("\tEnabled ");
            }
            else
            {
                print_str("\tDisabled");
            }
        }
        if (showSlumber)
        {
            if (enhPhyControlData[phyIter].enableSlumber)
            {
                print_str("\tEnabled ");
            }
            else
            {
                print_str("\tDisabled");
            }
        }
        print_str("\n");
    }
    print_str("\n");
}

eReturnValues get_PUIS_Info(const tDevice* device, ptrPuisInfo info)
{
    eReturnValues ret = NOT_SUPPORTED;

    if (info == M_NULLPTR)
    {
        return BAD_PARAMETER;
    }

    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        ret = SUCCESS;
        // set everything to false then verify the identify bits
        info->puisSupported         = false;
        info->puisEnabled           = false;
        info->spinupCommandRequired = false;
        if (is_ATA_Identify_Word_Valid_With_Bits_14_And_15(le16_to_host(device->drive_info.IdentifyData.ata.Word083)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word083) & BIT5)
        {
            info->puisSupported = true;
        }
        if (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word086)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word086) & BIT5)
        {
            info->puisEnabled = true;
        }
        if ((is_ATA_Identify_Word_Valid_With_Bits_14_And_15(
                 le16_to_host(device->drive_info.IdentifyData.ata.Word083)) &&
             le16_to_host(device->drive_info.IdentifyData.ata.Word083) & BIT6) ||
            (is_ATA_Identify_Word_Valid(le16_to_host(device->drive_info.IdentifyData.ata.Word086)) &&
             le16_to_host(device->drive_info.IdentifyData.ata.Word086) & BIT6))
        {
            info->spinupCommandRequired = true;
        }
    }
    return ret;
}

eReturnValues enable_Disable_PUIS_Feature(const tDevice* device, bool enable)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        // check the identify bits to make sure PUIS is supported.
        if (is_ATA_Identify_Word_Valid_With_Bits_14_And_15(le16_to_host(device->drive_info.IdentifyData.ata.Word083)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word083) & BIT5)
        {
            if (enable)
            {
                ret = ata_SF_PUIS(device, ATA_SF_ENABLE);
            }
            else
            {
                ret = ata_SF_PUIS(device, ATA_SF_DISABLE);
            }
        }
    }
    return ret;
}

eReturnValues puis_Spinup(const tDevice* device)
{
    eReturnValues ret = NOT_SUPPORTED;
    if (device->drive_info.drive_type == ATA_DRIVE)
    {
        // check the identify bits to make sure PUIS is supported.
        if (is_ATA_Identify_Word_Valid_With_Bits_14_And_15(le16_to_host(device->drive_info.IdentifyData.ata.Word083)) &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word083) & BIT5 &&
            le16_to_host(device->drive_info.IdentifyData.ata.Word083) & BIT6)
        {
            ret = ata_SF_PUIS_Spinup(device);
        }
        else
        {
            // this command is not required to spinup the drive. Any media access will spin it up.
        }
    }
    return ret;
}
