// 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.h
// \brief This file defines the functions for power related changes to drives.

#pragma once

#include "operations_Common.h"

#if defined(__cplusplus)
extern "C"
{
#endif

    //-----------------------------------------------------------------------------
    //
    //  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
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues enable_Disable_EPC_Feature(const tDevice* M_NONNULL device,
                                                                    eEPCFeatureSet           lba_field);

    //-----------------------------------------------------------------------------
    //
    //  print_Current_Power_Mode( tDevice* M_NONNULL device )
    //
    //! \brief   Checks the current power mode of the device and prints it to the screen. (SATA will always work. SAS
    //! only works if the drive has transitioned to another state)
    //
    //  Entry:
    //!   \param  device file descriptor
    //!
    //  Exit:
    //!   \return SUCCESS = good, !SUCCESS something went wrong see error codes
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1) OPENSEA_OPERATIONS_API eReturnValues print_Current_Power_Mode(const tDevice* M_NONNULL device);

    //-----------------------------------------------------------------------------
    //
    //  transition_Power_State( tDevice* M_NONNULL device, ePowerConditionID newState); )
    //
    //! \brief  Transition the device from one power state to another.
    //
    //  Entry:
    //!   \param device - file descriptor
    //!   \param newState - State the device is desired to be transitioned to.
    //!                     e.g. PWR_CND_IDLE_A, PWR_CND_IDLE_B etc.
    //!
    //  Exit:
    //!   \return SUCCESS = good, !SUCCESS something went wrong see error codes
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues transition_Power_State(const tDevice* M_NONNULL device,
                                                                ePowerConditionID        newState);

    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues transition_NVM_Power_State(const tDevice* M_NONNULL device, uint8_t newState);
    // the npss can technically report up to 256 since it is a 8bit field, but spec only defines 32 (0 to 31)
#define MAXIMUM_NVME_POWER_STATES UINT16_C(32)

    // note: this does not exactly match the NVMe identify data. It takes that and converts it to this "friendly"
    // structure that is a little easier to parse
    typedef struct s_nvmePowerState
    {
        uint16_t powerStateNumber; // from 0 - 31
        bool     maxPowerValid;    // may not be valid for some states
        uint32_t maxPowerMilliWatts;
        bool     isNonOperationalPS; // if true, commands are not processed in this state
        uint32_t entryLatency;
        uint32_t exitLatency;
        uint8_t  relativeReadThroughput;
        uint8_t  relativeReadLatency;
        uint8_t  relativeWriteThroughput;
        uint8_t  relativeWriteLatency;
        bool     idlePowerValid;
        uint32_t idlePowerMilliWatts;
        bool     activePowerValid;
        uint32_t activePowerMilliWatts;
        uint8_t  activePowerWorkload;
    } nvmePowerState;

    typedef struct s_nvmeSupportedPowerStates
    {
        uint16_t       numberOfPowerStates; // how many were filled into the following array
        uint32_t       activePowerState;    // which state is active. 0 - 31
        nvmePowerState powerState[MAXIMUM_NVME_POWER_STATES];
    } nvmeSupportedPowerStates, *ptrNVMeSupportedPowerStates;

    M_PARAM_RO(1)
    M_PARAM_WO(2)
    OPENSEA_OPERATIONS_API eReturnValues get_NVMe_Power_States(const tDevice* M_NONNULL              device,
                                                               ptrNVMeSupportedPowerStates M_NONNULL nvmps);

    M_PARAM_RO(1) OPENSEA_OPERATIONS_API void print_NVM_Power_States(ptrNVMeSupportedPowerStates M_NONNULL nvmps);

    //-----------------------------------------------------------------------------
    //
    //  get_Power_State(const tDevice *device, uint32_t * powerState, eFeatureModeSelect selectValue )
    //
    //! \brief  Get the current NVMe power state
    //
    //  Entry:
    //!   \param [in] device - file descriptor
    //!   \param [out] powerState - The power state the device currently is in
    //!   \param [in] selectValue - enum to say Current or default etc.
    //!
    //  Exit:
    //!   \return SUCCESS = good, !SUCCESS something went wrong see error codes
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1)
    M_PARAM_WO(2)
    OPENSEA_OPERATIONS_API eReturnValues get_Power_State(const tDevice* M_NONNULL device,
                                                         uint32_t* M_NONNULL      powerState,
                                                         eFeatureModeSelect       selectValue);

    typedef struct s_powerConsumptionIdentifier
    {
        uint8_t  identifierValue;
        uint8_t  units;
        uint16_t value;
    } powerConsumptionIdentifier;

    typedef struct s_powerConsumptionCOntrolField
    {
        uint8_t controlIdentifier;
        uint8_t activeLevel;
    } powerConsumptionControlField;

// Maximum number of power consumption identifiers...probably won't get this many, but
// might as well make this possible to do.
#define MAX_POWER_CONSUMPTION_IDENTIFIERS 0xFF

    typedef struct s_powerConsumptionIdentifiers
    {
        uint8_t                      numberOfPCIdentifiers;
        powerConsumptionIdentifier   identifiers[MAX_POWER_CONSUMPTION_IDENTIFIERS];
        bool                         currentIdentifierValid;
        powerConsumptionControlField currentControlField;
        powerConsumptionControlField defaultControlField;
        bool activeLevelChangable; // This may be changable or not depending on what the drive reports, valid for SAS
    } powerConsumptionIdentifiers, *ptrPowerConsumptionIdentifiers;

    //-----------------------------------------------------------------------------
    //
    //  get_Power_Consumption_Identifiers(const tDevice *device, ptrPowerConsumptionIdentifiers identifiers)
    //
    //! \brief  Get all the power consumption identifiers from a SCSI device.
    //
    //  Entry:
    //!   \param [in] device - file descriptor
    //!   \param [out] identifiers - pointer to a struct that will be filled in with power consumption information
    //!
    //  Exit:
    //!   \return SUCCESS = good, !SUCCESS something went wrong see error codes
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1)
    M_PARAM_WO(2)
    OPENSEA_OPERATIONS_API eReturnValues
    get_Power_Consumption_Identifiers(const tDevice* M_NONNULL                 device,
                                      ptrPowerConsumptionIdentifiers M_NONNULL identifiers);

    //-----------------------------------------------------------------------------
    //
    //  print_Power_Consumption_Identifiers(ptrPowerConsumptionIdentifiers identifiers)
    //
    //! \brief  Print out all the power consumption identifiers in the struct (the number that is supported anyways).
    //
    //  Entry:
    //!   \param [in] device - file descriptor
    //!   \param [in] identifiers - pointer to a struct containing the power consumption identifier information
    //!
    //  Exit:
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1)
    M_PARAM_RO(2)
    OPENSEA_OPERATIONS_API void print_Power_Consumption_Identifiers(const tDevice* M_NONNULL device,
                                                                    ptrPowerConsumptionIdentifiers M_NONNULL
                                                                        identifiers);

    typedef enum ePCActiveLevelEnum
    {
        PC_ACTIVE_LEVEL_IDENTIFIER   = 0,
        PC_ACTIVE_LEVEL_HIGHEST      = 1,
        PC_ACTIVE_LEVEL_INTERMEDIATE = 2,
        PC_ACTIVE_LEVEL_LOWEST       = 3
    } ePCActiveLevel;

    //-----------------------------------------------------------------------------
    //
    //  set_Power_Consumption(const tDevice *device, ePCActiveLevel activeLevelField, uint8_t
    //  powerConsumptionIdentifier, bool resetToDefault)
    //
    //! \brief  Set the power consumption rate for a SCSI drive. (ATA not supported right now, may never be depending on
    //! future ata spec implementation)
    //
    //  Entry:
    //!   \param [in] device - file descriptor
    //!   \param [in] activeLevelField - set to an enum value matching SCP spec. When set to PC_ACTIVE_LEVEL_IDENTIFIER,
    //!                                  the powerConsumptionIdentifier value will be used.
    //!   \param [in] powerConsumptionIdentifier - only valid when activeLevelField is set to
    //!   PC_ACTIVE_LEVEL_IDENTIFIER. This value must match one the device supports.
    //!   \param [in] resetToDefault - when set to true, all other inputs are ignored. The default mode is restored by
    //!                                reading the default settings and setting the current settings to the defaults.
    //!   \param [in] disableFeature - when set to true, disable the feature and all other inputs are ignored.
    //!
    //  Exit:
    //!   \return SUCCESS = good, !SUCCESS something went wrong see error codes
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues set_Power_Consumption(const tDevice* M_NONNULL device,
                                                               ePCActiveLevel           activeLevelField,
                                                               uint8_t                  powerConsumptionIdentifier,
                                                               bool                     resetToDefault,
                                                               bool                     disableFeature);

    //-----------------------------------------------------------------------------
    //
    //  map_Watt_Value_To_Power_Consumption_Identifier(const tDevice *device, double watts, uint8_t
    //  *powerConsumptionIdentifier)
    //
    //! \brief  Maps a value in Watts to a power condition identifier supported by the drive. Will return NOT_SUPPORTED
    //! if no suitable match can be found
    //
    //  Entry:
    //!   \param [in] device - file descriptor
    //!   \param [in] watts - value in watts to search for. Will be rounded down when searching for a match.
    //!   \param [out] powerConsumptionIdentifier - will be set to the value of the power consumption identifier that
    //!   most closely matches the incoming watt value.
    //!
    //  Exit:
    //!   \return SUCCESS = good, !SUCCESS something went wrong see error codes
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1)
    M_PARAM_WO(3)
    OPENSEA_OPERATIONS_API eReturnValues
    map_Watt_Value_To_Power_Consumption_Identifier(const tDevice* M_NONNULL device,
                                                   double                   watts,
                                                   uint8_t* M_NONNULL       powerConsumptionIdentifier);

    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues enable_Disable_APM_Feature(const tDevice* M_NONNULL device, bool enable);

    M_PARAM_RO(1) OPENSEA_OPERATIONS_API eReturnValues set_APM_Level(const tDevice* M_NONNULL device, uint8_t apmLevel);

    M_PARAM_RO(1)
    M_PARAM_WO(2)
    OPENSEA_OPERATIONS_API eReturnValues get_APM_Level(const tDevice* M_NONNULL device, uint8_t* M_NONNULL apmLevel);
    typedef struct s_powerConditionInfo // written according to ATA spec fields...will try to populate as much as
                                        // possible that's similar for SCSI
    {
        bool     powerConditionSupported;
        bool     powerConditionSaveable;
        bool     powerConditionChangeable;
        bool     defaultTimerEnabled;
        bool     savedTimerEnabled;
        bool     currentTimerEnabled;
        bool     holdPowerConditionNotSupported;
        uint32_t defaultTimerSetting;
        uint32_t savedTimerSetting;
        uint32_t currentTimerSetting;
        uint32_t nominalRecoveryTimeToActiveState;
        uint32_t minimumTimerSetting;
        uint32_t maximumTimerSetting;
    } powerConditionInfo, *ptrPowerConditionInfo;

    typedef struct s_epcSettings
    {
        powerConditionInfo idle_a;
        powerConditionInfo idle_b;
        powerConditionInfo idle_c;
        powerConditionInfo standby_y;
        powerConditionInfo standby_z;
        bool               settingsAffectMultipleLogicalUnits;
    } epcSettings, *ptrEpcSettings;

    M_PARAM_RO(1)
    M_PARAM_RW(2)
    OPENSEA_OPERATIONS_API eReturnValues get_EPC_Settings(const tDevice* M_NONNULL device,
                                                          ptrEpcSettings M_NONNULL epcSettings);

    M_PARAM_RO(1)
    M_PARAM_RO(2)
    OPENSEA_OPERATIONS_API
    void print_EPC_Settings(const tDevice* M_NONNULL device, ptrEpcSettings M_NONNULL epcSettings);

    M_PARAM_RO(1)
    M_PARAM_WO(2)
    M_PARAM_WO(3)
    OPENSEA_OPERATIONS_API eReturnValues
    sata_Get_Device_Initiated_Interface_Power_State_Transitions(const tDevice* M_NONNULL device,
                                                                bool* M_NULLABLE         supported,
                                                                bool* M_NULLABLE         enabled);

    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues
    sata_Set_Device_Initiated_Interface_Power_State_Transitions(const tDevice* M_NONNULL device, bool enable);

    M_PARAM_RO(1)
    M_PARAM_WO(2)
    M_PARAM_WO(3)
    OPENSEA_OPERATIONS_API eReturnValues
    sata_Get_Device_Automatic_Partial_To_Slumber_Transtisions(const tDevice* M_NONNULL device,
                                                              bool* M_NULLABLE         supported,
                                                              bool* M_NULLABLE         enabled);

    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues
    sata_Set_Device_Automatic_Partial_To_Slumber_Transtisions(const tDevice* M_NONNULL device, bool enable);

    // Following functions allow power mode transitions on Non-EPC drives (pre SBC3 for SCSI)
    // These will work with EPC, but may cause changes to the current timer values. See SCSI/ATA specs for details on
    // interactions with these and EPC timers
    M_PARAM_RO(1) OPENSEA_OPERATIONS_API eReturnValues transition_To_Active(const tDevice* M_NONNULL device);

    M_PARAM_RO(1) OPENSEA_OPERATIONS_API eReturnValues transition_To_Standby(const tDevice* M_NONNULL device);

    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues transition_To_Idle(const tDevice* M_NONNULL device,
                                                            bool unload); // unload feature must be supported

    // NOTE: Do not call this unless you know what you are doing. This requires a reset to wake up from, which may not
    // be callable from an application.
    M_PARAM_RO(1) OPENSEA_OPERATIONS_API eReturnValues transition_To_Sleep(const tDevice* M_NONNULL device);

    // Be careful changing partial and slumber settings. Not every controller will support it properly!
    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues scsi_Set_Partial_Slumber(const tDevice* M_NONNULL device,
                                                                  bool                     enablePartial,
                                                                  bool                     enableSlumber,
                                                                  bool                     partialValid,
                                                                  bool                     slumberValid,
                                                                  bool                     allPhys,
                                                                  uint8_t                  phyNumber);

    M_PARAM_RO(1)
    M_PARAM_WO(2)
    OPENSEA_OPERATIONS_API eReturnValues get_SAS_Enhanced_Phy_Control_Number_Of_Phys(const tDevice* M_NONNULL device,
                                                                                     uint8_t* M_NONNULL       phyCount);

    typedef struct s_sasEnhPhyControl
    {
        uint8_t phyIdentifier;
        bool    enablePartial;
        bool    enableSlumber;
    } sasEnhPhyControl, *ptrSasEnhPhyControl;

    static M_INLINE void safe_free_sasEnhPhyControl(sasEnhPhyControl* M_NULLABLE* M_NULLABLE mem)
    {
        safe_free_core(M_REINTERPRET_CAST(void**, mem));
    }

    // If doing all phys, use get_SAS_Enhanced_Phy_Control_Number_Of_Phys first to figure out how much memory must be
    // allocated
    M_PARAM_RO(1)
    M_PARAM_WO(4)
    OPENSEA_OPERATIONS_API eReturnValues
    get_SAS_Enhanced_Phy_Control_Partial_Slumber_Settings(const tDevice* M_NONNULL      device,
                                                          bool                          allPhys,
                                                          uint8_t                       phyNumber,
                                                          ptrSasEnhPhyControl M_NONNULL enhPhyControlData,
                                                          uint32_t                      enhPhyControlDataSize);

    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API void show_SAS_Enh_Phy_Control_Partial_Slumber(ptrSasEnhPhyControl M_NONNULL
                                                                                  enhPhyControlData,
                                                                         uint32_t enhPhyControlDataSize,
                                                                         bool     showPartial,
                                                                         bool     showSlumber);

    typedef struct s_powerConditionSettings
    {
        bool     powerConditionValid; // specifies whether to look at anything in this structure or not.
        bool     restoreToDefault;    // If this is set, none of the other values in this matter
        bool     enableValid;         // holds if enable bool below should be referenced at all or not.
        bool     enable;              // set to false to disable when enableValid is set to true
        bool     timerValid;          // set to true when the below timer has a valid value to use
        uint32_t timerInHundredMillisecondIncrements;
    } powerConditionSettings, *ptrPowerConditionSettings;

    typedef struct s_powerConditionTimers
    {
        union
        {
            powerConditionSettings idle;
            powerConditionSettings idle_a;
        };
        union
        {
            powerConditionSettings standby;
            powerConditionSettings standby_z;
        };
        // All fields below here only apply to EPC. Everything above is supported by legacy devices that support the
        // power conditions mode page
        powerConditionSettings idle_b;
        powerConditionSettings idle_c;
        powerConditionSettings standby_y;
        // Fields below are only for SAS/SCSI devices. Attempting to change these for other devices will be ignored.
        // PM_BG_Preference and CCF fields are handled below. EPC only
        bool    powerModeBackgroundValid;
        bool    powerModeBackgroundResetDefault; // reset this to default value
        uint8_t powerModeBackGroundRelationShip;
        struct
        {
            bool    ccfIdleValid;
            bool    ccfStandbyValid;
            bool    ccfStopValid;
            bool    ccfIdleResetDefault;
            bool    ccfStandbyResetDefault;
            bool    ccfStopResetDefault;
            uint8_t ccfIdleMode;
            uint8_t ccfStandbyMode;
            uint8_t ccfStopMode;
        } checkConditionFlags;
    } powerConditionTimers, *ptrPowerConditionTimers;

    M_PARAM_RO(1)
    M_PARAM_RO(3)
    OPENSEA_OPERATIONS_API eReturnValues scsi_Set_Power_Conditions(const tDevice* M_NONNULL device,
                                                                   bool                     restoreAllToDefaults,
                                                                   ptrPowerConditionTimers M_NONNULL powerConditions,
                                                                   bool                              saveChanges);

    M_PARAM_RO(1)
    M_PARAM_RO(3)
    OPENSEA_OPERATIONS_API eReturnValues set_EPC_Power_Conditions(const tDevice* M_NONNULL device,
                                                                  bool                     restoreAllToDefaults,
                                                                  ptrPowerConditionTimers M_NONNULL powerConditions,
                                                                  bool                              saveChanges);

    M_PARAM_RO(1)
    M_PARAM_RO(3)
    M_PARAM_RO(4)
    OPENSEA_OPERATIONS_API eReturnValues
    scsi_Set_Legacy_Power_Conditions(const tDevice* M_NONNULL             device,
                                     bool                                 restoreAllToDefaults,
                                     ptrPowerConditionSettings M_NULLABLE standbyTimer,
                                     ptrPowerConditionSettings M_NULLABLE idleTimer,
                                     bool                                 saveChanges);

    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues scsi_Set_Standby_Timer_State(const tDevice* M_NONNULL device,
                                                                      bool                     enable,
                                                                      bool                     saveChanges);

    // When ATA drive, the restoreToDefaults is not allowed. Also, translation of timer value is done according to SAT
    // spec
    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues set_Standby_Timer(const tDevice* M_NONNULL device,
                                                           uint32_t                 hundredMillisecondIncrements,
                                                           bool                     restoreToDefault,
                                                           bool                     saveChanges);

    // SCSI/SAS Only
    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues scsi_Set_Idle_Timer_State(const tDevice* M_NONNULL device,
                                                                   bool                     enable,
                                                                   bool                     saveChanges);

    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues set_Idle_Timer(const tDevice* M_NONNULL device,
                                                        uint32_t                 hundredMillisecondIncrements,
                                                        bool                     restoreToDefault,
                                                        bool                     saveChanges);

    //-----------------------------------------------------------------------------
    //
    //  enable_Disable_PUIS_Feature(const tDevice *device, bool enable)
    //
    //! \brief   Enables or disables the SATA PUIS feature
    //
    //  Entry:
    //!   \param device - file descriptor
    //!   \param enable - set to true to enable the PUIS feature. Set to False to disable the PUIS feature.
    //!
    //  Exit:
    //!   \return SUCCESS = successfully determined erase support, anything else = some error occured while determining
    //!   support.
    //
    //-----------------------------------------------------------------------------
    M_PARAM_RO(1)
    OPENSEA_OPERATIONS_API eReturnValues enable_Disable_PUIS_Feature(const tDevice* M_NONNULL device, bool enable);

    M_PARAM_RO(1) OPENSEA_OPERATIONS_API eReturnValues puis_Spinup(const tDevice* M_NONNULL device);

    typedef struct s_puisInfo
    {
        bool puisSupported;
        bool puisEnabled;
        bool spinupCommandRequired; // requires the spinup command to be issued to spin up. If false, then a media
                                    // access will spinup as needed.
    } puisInfo, *ptrPuisInfo;

    M_PARAM_RO(1)
    M_PARAM_WO(2)
    OPENSEA_OPERATIONS_API eReturnValues get_PUIS_Info(const tDevice* M_NONNULL device, ptrPuisInfo M_NONNULL info);

#if defined(__cplusplus)
}
#endif
