Add support for OEM Power Modes

- Allow mode to be set via PassThrough interface
- Allow non-customer OEM power modes to be persisted
- Persist any OEM power mode settings
- moved mode related code from Status to PowerMode object
- merged PowerIPS into PowerMode object

Tested on Everest and Rainier.
Setting mode through PassThrough/ce-login:
  busctl call org.open_power.OCC.Control /org/open_power/control/occ0 org.open_power.OCC.PassThrough SetMode yq 11 3600
Trace (via PassThrough interface)
  openpower-occ-control[4440]: PassThrough::setMode() Setting Power Mode 11 (data: 3600)
  openpower-occ-control[4440]: PowerMode::sendModeChange: SET_MODE(11,3600) command to OCC0 (9 bytes)
Trace (setting mode via GUI/Redfish):
  openpower-occ-control[4440]: Power Mode Change Requested: xyz.openbmc_project.Control.Power.Mode.PowerMode.MaximumPerformance
  openpower-occ-control[4440]: PowerMode::sendModeChange: SET_MODE(12,0) command to OCC0 (9 bytes)
Verified when system in any OEM mode that Redfish also reports OEM
Verified all modes are persisted across PM Complex resets and reboots

Change-Id: Idd0be05cb6fd74dbd0776145f212c49addd1c365
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/meson.build b/meson.build
index 2f7a8f6..843a9a7 100644
--- a/meson.build
+++ b/meson.build
@@ -20,6 +20,8 @@
 conf_data.set_quoted('OCC_NAME', 'occ')
 conf_data.set_quoted('OCC_MASTER_NAME', 'occ-hwmon.1')
 conf_data.set_quoted('OCC_DEV_PATH', '/dev/occ')
+conf_data.set_quoted('CPU_SUBPATH', '/xyz/openbmc_project/inventory/system/chassis/motherboard')
+conf_data.set_quoted('OCC_CONTROL_PERSIST_PATH', '/var/lib/openpower-occ-control')
 
 conf_data.set('MAX_CPUS', get_option('max-cpus'))
 conf_data.set('OCC_CPU_TEMP_SENSOR_TYPE', 0xC0)
diff --git a/occ_command.hpp b/occ_command.hpp
index ff8347b..aee4c58 100644
--- a/occ_command.hpp
+++ b/occ_command.hpp
@@ -45,10 +45,11 @@
 enum class SysPwrMode
 {
     NO_CHANGE = 0,
-    DISABLE = 0x01,      // Disable / Static Base Frequencey
+    STATIC = 0x01,       // Static Base Frequencey
     SFP = 0x03,          // Static Frequency Point (requires freqPt)
     SAFE = 0x04,         // reported when system is in SAFE mode (not settable)
     POWER_SAVING = 0x05, // Static Power Saving
+    MAX_FREQ = 0x09,     // Maximum Frequency (per chip)
     DYNAMIC_PERF = 0x0A, // Dynamic / Balanced Performance
     FFO = 0x0B,          // Fixed Frequency Override (requires freqPt)
     MAX_PERF = 0x0C      // Maximum Performance
@@ -56,8 +57,11 @@
 
 // Only some of the SysPwrModes are currently supported and allowed to be set
 #define VALID_POWER_MODE_SETTING(mode)                                         \
-    ((mode == SysPwrMode::DISABLE) || (mode == SysPwrMode::POWER_SAVING) ||    \
+    ((mode == SysPwrMode::STATIC) || (mode == SysPwrMode::POWER_SAVING) ||     \
      (mode == SysPwrMode::DYNAMIC_PERF) || (mode == SysPwrMode::MAX_PERF))
+#define VALID_OEM_POWER_MODE_SETTING(mode)                                     \
+    ((mode == SysPwrMode::SFP) || (mode == SysPwrMode::FFO) ||                 \
+     (mode == SysPwrMode::MAX_FREQ))
 
 enum class RspStatus
 {
diff --git a/occ_manager.cpp b/occ_manager.cpp
index 9b6f1c5..d24aa68 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -13,6 +13,7 @@
 #include <chrono>
 #include <cmath>
 #include <filesystem>
+#include <fstream>
 #include <regex>
 
 namespace open_power
@@ -132,11 +133,11 @@
 {
     auto path = fs::path(OCC_CONTROL_ROOT) / occ;
 
-    passThroughObjects.emplace_back(
-        std::make_unique<PassThrough>(path.c_str()));
-
     statusObjects.emplace_back(std::make_unique<Status>(
         event, path.c_str(), *this,
+#ifdef POWER10
+        pmode,
+#endif
         std::bind(std::mem_fn(&Manager::statusCallBack), this,
                   std::placeholders::_1)
 #ifdef PLDM
@@ -146,27 +147,37 @@
 #endif
             ));
 
-    // Create the power cap monitor object for master occ (0)
-    if (!pcap)
+    if (statusObjects.back()->isMasterOcc())
     {
-        pcap = std::make_unique<open_power::occ::powercap::PowerCap>(
-            *statusObjects.front());
-    }
+        log<level::INFO>(
+            fmt::format("Manager::createObjects(): OCC{} is the master",
+                        statusObjects.back()->getOccInstanceID())
+                .c_str());
+        _pollTimer->setEnabled(false);
+
+        // Create the power cap monitor object for master OCC
+        if (!pcap)
+        {
+            pcap = std::make_unique<open_power::occ::powercap::PowerCap>(
+                *statusObjects.front());
+        }
 
 #ifdef POWER10
-    // Create the power mode monitor object for master occ (0)
-    if (!pmode)
-    {
-        pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
-            *statusObjects.front());
-    }
-    // Create the idle power saver monitor object for master occ (0)
-    if (!pips)
-    {
-        pips = std::make_unique<open_power::occ::powermode::PowerIPS>(
-            *statusObjects.front());
-    }
+        // Create the power mode object for master OCC
+        if (!pmode)
+        {
+            pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
+                *this, path.c_str());
+        }
 #endif
+    }
+
+    passThroughObjects.emplace_back(std::make_unique<PassThrough>(path.c_str()
+#ifdef POWER10
+                                                                      ,
+                                                                  pmode
+#endif
+                                                                  ));
 }
 
 void Manager::statusCallBack(bool status)
@@ -214,9 +225,8 @@
         if (!_pollTimer->isEnabled())
         {
             log<level::INFO>(
-                fmt::format(
-                    "Manager::statusCallBack(): {} OCCs will be polled every {} seconds",
-                    activeCount, pollInterval)
+                fmt::format("Manager: OCCs will be polled every {} seconds",
+                            pollInterval)
                     .c_str());
 
             // Send poll and start OCC poll timer
@@ -280,9 +290,7 @@
         *statusObjects.front(), occMasterName);
 #ifdef POWER10
     pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
-        *statusObjects.front());
-    pips = std::make_unique<open_power::occ::powermode::PowerIPS>(
-        *statusObjects.front());
+        *this, path.c_str());
 #endif
 }
 #endif
@@ -1013,7 +1021,8 @@
     else
     {
         log<level::INFO>(
-            fmt::format("validateOccMaster: OCC{} is master", masterInstance)
+            fmt::format("validateOccMaster: OCC{} is master of {} OCCs",
+                        masterInstance, activeCount)
                 .c_str());
     }
 }
diff --git a/occ_manager.hpp b/occ_manager.hpp
index e9a469c..9a29b8f 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -190,9 +190,6 @@
 #ifdef POWER10
     /** @brief Power mode monitor and notification object */
     std::unique_ptr<open_power::occ::powermode::PowerMode> pmode;
-
-    /** @brief Idle Power Saver monitor and notification object */
-    std::unique_ptr<open_power::occ::powermode::PowerIPS> pips;
 #endif
 
     /** @brief sbdbusplus match objects */
diff --git a/occ_pass_through.cpp b/occ_pass_through.cpp
index db133b1..6cf6374 100644
--- a/occ_pass_through.cpp
+++ b/occ_pass_through.cpp
@@ -22,8 +22,21 @@
 namespace occ
 {
 
-PassThrough::PassThrough(const char* path) :
-    Iface(utils::getBus(), path), path(path),
+using namespace phosphor::logging;
+using namespace sdbusplus::org::open_power::OCC::Device::Error;
+
+PassThrough::PassThrough(
+    const char* path
+#ifdef POWER10
+    ,
+    std::unique_ptr<open_power::occ::powermode::PowerMode>& powerModeRef
+#endif
+    ) :
+    Iface(utils::getBus(), path),
+    path(path),
+#ifdef POWER10
+    pmode(powerModeRef),
+#endif
     devicePath(OCC_DEV_PATH + std::to_string((this->path.back() - '0') + 1)),
     occInstance(this->path.back() - '0'),
     activeStatusSignal(
@@ -60,9 +73,6 @@
 
 std::vector<uint8_t> PassThrough::send(std::vector<uint8_t> command)
 {
-    using namespace phosphor::logging;
-    using namespace sdbusplus::org::open_power::OCC::Device::Error;
-
     std::vector<uint8_t> response{};
 
     log<level::DEBUG>(
@@ -100,6 +110,48 @@
     return response;
 }
 
+bool PassThrough::setMode(const uint8_t mode, const uint16_t modeData)
+{
+#ifdef POWER10
+    SysPwrMode newMode = SysPwrMode(mode);
+
+    if ((!VALID_POWER_MODE_SETTING(newMode)) &&
+        (!VALID_OEM_POWER_MODE_SETTING(newMode)))
+    {
+        log<level::ERR>(
+            fmt::format(
+                "PassThrough::setMode() Unsupported mode {} requested (0x{:04X})",
+                newMode, modeData)
+                .c_str());
+        return false;
+    }
+
+    if (((newMode == SysPwrMode::FFO) || (newMode == SysPwrMode::SFP)) &&
+        (modeData == 0))
+    {
+        log<level::ERR>(
+            fmt::format(
+                "PassThrough::setMode() Mode {} requires non-zero frequency point.",
+                newMode)
+                .c_str());
+        return false;
+    }
+
+    log<level::INFO>(
+        fmt::format("PassThrough::setMode() Setting Power Mode {} (data: {})",
+                    newMode, modeData)
+            .c_str());
+    return pmode->setMode(newMode, modeData);
+#else
+    log<level::DEBUG>(
+        fmt::format(
+            "PassThrough::setMode() No support to setting Power Mode {} (data: {})",
+            mode, modeData)
+            .c_str());
+    return false;
+#endif
+}
+
 // Called at OCC Status change signal
 void PassThrough::activeStatusEvent(sdbusplus::message::message& msg)
 {
diff --git a/occ_pass_through.hpp b/occ_pass_through.hpp
index a1e1e91..52a8f71 100644
--- a/occ_pass_through.hpp
+++ b/occ_pass_through.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "occ_command.hpp"
+#include "powermode.hpp"
 #include "utils.hpp"
 
 #include <fmt/core.h>
@@ -39,7 +40,13 @@
     /** @brief Ctor to put pass-through d-bus object on the bus
      *  @param[in] path - Path to attach at
      */
-    explicit PassThrough(const char* path);
+    explicit PassThrough(
+        const char* path
+#ifdef POWER10
+        ,
+        std::unique_ptr<open_power::occ::powermode::PowerMode>& powerModeRef
+#endif
+    );
 
     /** @brief Pass through command to OCC from dbus
      *  @param[in] command - command to pass-through
@@ -53,10 +60,24 @@
      */
     std::vector<std::uint8_t> send(std::vector<std::uint8_t> command);
 
+    /** @brief Set a Power Mode
+     *
+     *  @param[in] mode - desired System Power Mode
+     *  @param[in] modeData - data associated some Power Modes
+     *
+     *  @returns true if mode change was accepted
+     */
+    bool setMode(const uint8_t mode, const uint16_t modeData);
+
   private:
     /** @brief Pass-through occ path on the bus */
     std::string path;
 
+#ifdef POWER10
+    /** @brief OCC PowerMode object */
+    std::unique_ptr<open_power::occ::powermode::PowerMode>& pmode;
+#endif
+
     /** @brief OCC device path
      *  For now, here is the hard-coded mapping until
      *  the udev rule is in.
diff --git a/occ_status.cpp b/occ_status.cpp
index a2ab6ab..1da440c 100644
--- a/occ_status.cpp
+++ b/occ_status.cpp
@@ -46,13 +46,12 @@
         }
         else
         {
-            // Call into Manager to let know that we will unbind.
-            if (this->callBack)
-            {
-                this->callBack(value);
-            }
-
 #ifdef POWER10
+            if (device.master())
+            {
+                // Prevent mode changes
+                pmode->setMasterActive(false);
+            }
             if (safeStateDelayTimer.isEnabled())
             {
                 // stop safe delay timer
@@ -60,6 +59,12 @@
             }
 #endif
 
+            // Call into Manager to let know that we will unbind.
+            if (this->callBack)
+            {
+                this->callBack(value);
+            }
+
             // Stop watching for errors
             removeErrorWatch();
 
@@ -103,6 +108,11 @@
 // Callback handler when a device error is reported.
 void Status::deviceError()
 {
+#ifdef POWER10
+    // Prevent mode changes
+    pmode->setMasterActive(false);
+#endif
+
     // This would deem OCC inactive
     this->occActive(false);
 
@@ -196,6 +206,9 @@
             {
                 if (device.master())
                 {
+                    // Prevent mode changes
+                    pmode->setMasterActive();
+
                     // Special processing by master OCC when it goes active
                     occsWentActive();
                 }
@@ -238,189 +251,12 @@
 }
 
 #ifdef POWER10
-// Check if Hypervisor target is PowerVM
-bool Status::isPowerVM()
-{
-    using namespace open_power::occ::powermode;
-    namespace Hyper = sdbusplus::com::ibm::Host::server;
-    constexpr auto HYPE_PATH = "/com/ibm/host0/hypervisor";
-    constexpr auto HYPE_INTERFACE = "com.ibm.Host.Target";
-    constexpr auto HYPE_PROP = "Target";
-
-    bool powerVmTarget = false;
-
-    // This will throw exception on failure
-    auto& bus = utils::getBus();
-    auto service = utils::getService(HYPE_PATH, HYPE_INTERFACE);
-    auto method = bus.new_method_call(service.c_str(), HYPE_PATH,
-                                      "org.freedesktop.DBus.Properties", "Get");
-    method.append(HYPE_INTERFACE, HYPE_PROP);
-    auto reply = bus.call(method);
-
-    std::variant<std::string> hyperEntryValue;
-    reply.read(hyperEntryValue);
-    auto propVal = std::get<std::string>(hyperEntryValue);
-    if (Hyper::Target::convertHypervisorFromString(propVal) ==
-        Hyper::Target::Hypervisor::PowerVM)
-    {
-        powerVmTarget = true;
-    }
-
-    log<level::DEBUG>(
-        fmt::format("Status::isPowerVM returning {}", powerVmTarget).c_str());
-
-    return powerVmTarget;
-}
-
-// Get the requested power mode
-SysPwrMode Status::getMode()
-{
-    using namespace open_power::occ::powermode;
-    SysPwrMode pmode = SysPwrMode::NO_CHANGE;
-
-    // This will throw exception on failure
-    auto& bus = utils::getBus();
-    auto service = utils::getService(PMODE_PATH, PMODE_INTERFACE);
-    auto method = bus.new_method_call(service.c_str(), PMODE_PATH,
-                                      "org.freedesktop.DBus.Properties", "Get");
-    method.append(PMODE_INTERFACE, POWER_MODE_PROP);
-    auto reply = bus.call(method);
-
-    std::variant<std::string> stateEntryValue;
-    reply.read(stateEntryValue);
-    auto propVal = std::get<std::string>(stateEntryValue);
-    pmode = powermode::convertStringToMode(propVal);
-
-    log<level::DEBUG>(
-        fmt::format("Status::getMode returning {}", pmode).c_str());
-
-    return pmode;
-}
-
-// Get the requested power mode
-bool Status::getIPSParms(uint8_t& enterUtil, uint16_t& enterTime,
-                         uint8_t& exitUtil, uint16_t& exitTime)
-{
-    using namespace open_power::occ::powermode;
-    // Defaults:
-    bool ipsEnabled = false; // Disabled
-    enterUtil = 8;           // Enter Utilization (8%)
-    enterTime = 240;         // Enter Delay Time (240s)
-    exitUtil = 12;           // Exit Utilization (12%)
-    exitTime = 10;           // Exit Delay Time (10s)
-
-    std::map<std::string, std::variant<bool, uint8_t, uint64_t>>
-        ipsProperties{};
-
-    // Get all IPS properties from DBus
-    try
-    {
-        auto& bus = utils::getBus();
-        auto service = utils::getService(PIPS_PATH, PIPS_INTERFACE);
-        auto method =
-            bus.new_method_call(service.c_str(), PIPS_PATH,
-                                "org.freedesktop.DBus.Properties", "GetAll");
-        method.append(PIPS_INTERFACE);
-        auto reply = bus.call(method);
-        reply.read(ipsProperties);
-    }
-    catch (const sdbusplus::exception::exception& e)
-    {
-        log<level::ERR>(
-            fmt::format(
-                "Unable to read Idle Power Saver parameters so it will be disabled: {}",
-                e.what())
-                .c_str());
-        return ipsEnabled;
-    }
-
-    auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP);
-    if (ipsEntry != ipsProperties.end())
-    {
-        ipsEnabled = std::get<bool>(ipsEntry->second);
-    }
-    else
-    {
-        log<level::ERR>(
-            fmt::format("Status::getIPSParms could not find property: {}",
-                        IPS_ENABLED_PROP)
-                .c_str());
-    }
-
-    ipsEntry = ipsProperties.find(IPS_ENTER_UTIL);
-    if (ipsEntry != ipsProperties.end())
-    {
-        enterUtil = std::get<uint8_t>(ipsEntry->second);
-    }
-    else
-    {
-        log<level::ERR>(
-            fmt::format("Status::getIPSParms could not find property: {}",
-                        IPS_ENTER_UTIL)
-                .c_str());
-    }
-
-    ipsEntry = ipsProperties.find(IPS_ENTER_TIME);
-    if (ipsEntry != ipsProperties.end())
-    {
-        std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
-        enterTime =
-            std::chrono::duration_cast<std::chrono::seconds>(ms).count();
-    }
-    else
-    {
-        log<level::ERR>(
-            fmt::format("Status::getIPSParms could not find property: {}",
-                        IPS_ENTER_TIME)
-                .c_str());
-    }
-
-    ipsEntry = ipsProperties.find(IPS_EXIT_UTIL);
-    if (ipsEntry != ipsProperties.end())
-    {
-        exitUtil = std::get<uint8_t>(ipsEntry->second);
-    }
-    else
-    {
-        log<level::ERR>(
-            fmt::format("Status::getIPSParms could not find property: {}",
-                        IPS_EXIT_UTIL)
-                .c_str());
-    }
-
-    ipsEntry = ipsProperties.find(IPS_EXIT_TIME);
-    if (ipsEntry != ipsProperties.end())
-    {
-        std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
-        exitTime = std::chrono::duration_cast<std::chrono::seconds>(ms).count();
-    }
-    else
-    {
-        log<level::ERR>(
-            fmt::format("Status::getIPSParms could not find property: {}",
-                        IPS_EXIT_TIME)
-                .c_str());
-    }
-
-    if (enterUtil > exitUtil)
-    {
-        log<level::ERR>(
-            fmt::format(
-                "ERROR: Idle Power Saver Enter Utilization ({}%) is > Exit Utilization ({}%) - using Exit for both",
-                enterUtil, exitUtil)
-                .c_str());
-        enterUtil = exitUtil;
-    }
-
-    return ipsEnabled;
-}
-
 // Special processing that needs to happen once the OCCs change to ACTIVE state
 void Status::occsWentActive()
 {
     CmdStatus status = CmdStatus::SUCCESS;
 
-    status = sendModeChange();
+    status = pmode->sendModeChange();
     if (status != CmdStatus::SUCCESS)
     {
         log<level::ERR>(
@@ -430,7 +266,7 @@
                 .c_str());
     }
 
-    status = sendIpsData();
+    status = pmode->sendIpsData();
     if (status != CmdStatus::SUCCESS)
     {
         log<level::ERR>(
@@ -441,189 +277,6 @@
     }
 }
 
-// Send mode change request to the master OCC
-CmdStatus Status::sendModeChange()
-{
-    CmdStatus status = CmdStatus::FAILURE;
-
-    if (!device.master())
-    {
-        log<level::ERR>(
-            fmt::format(
-                "Status::sendModeChange: MODE CHANGE does not get sent to slave OCC{}",
-                instance)
-                .c_str());
-        return status;
-    }
-    if (!isPowerVM())
-    {
-        // Mode change is only supported on PowerVM systems
-        log<level::DEBUG>(
-            "Status::sendModeChange: MODE CHANGE does not get sent on non-PowerVM systems");
-        return CmdStatus::SUCCESS;
-    }
-
-    const SysPwrMode newMode = getMode();
-
-    if (VALID_POWER_MODE_SETTING(newMode))
-    {
-        std::vector<std::uint8_t> cmd, rsp;
-        cmd.reserve(9);
-        cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
-        cmd.push_back(0x00); // Data Length (2 bytes)
-        cmd.push_back(0x06);
-        cmd.push_back(0x30); // Data (Version)
-        cmd.push_back(uint8_t(OccState::NO_CHANGE));
-        cmd.push_back(uint8_t(newMode));
-        cmd.push_back(0x00); // Mode Data (Freq Point)
-        cmd.push_back(0x00); //
-        cmd.push_back(0x00); // reserved
-        log<level::INFO>(
-            fmt::format(
-                "Status::sendModeChange: SET_MODE({}) command to OCC{} ({} bytes)",
-                newMode, instance, cmd.size())
-                .c_str());
-        status = occCmd.send(cmd, rsp);
-        if (status == CmdStatus::SUCCESS)
-        {
-            if (rsp.size() == 5)
-            {
-                if (RspStatus::SUCCESS != RspStatus(rsp[2]))
-                {
-                    log<level::ERR>(
-                        fmt::format(
-                            "Status::sendModeChange: SET MODE failed with status 0x{:02X}",
-                            rsp[2])
-                            .c_str());
-                    dump_hex(rsp);
-                    status = CmdStatus::FAILURE;
-                }
-            }
-            else
-            {
-                log<level::ERR>(
-                    "Status::sendModeChange: INVALID SET MODE response");
-                dump_hex(rsp);
-                status = CmdStatus::FAILURE;
-            }
-        }
-        else
-        {
-            if (status == CmdStatus::OPEN_FAILURE)
-            {
-                log<level::INFO>("Status::sendModeChange: OCC not active yet");
-                status = CmdStatus::SUCCESS;
-            }
-            else
-            {
-                log<level::ERR>("Status::sendModeChange: SET_MODE FAILED!");
-            }
-        }
-    }
-    else
-    {
-        log<level::ERR>(
-            fmt::format(
-                "Status::sendModeChange: Unable to set power mode to {}",
-                newMode)
-                .c_str());
-        status = CmdStatus::FAILURE;
-    }
-
-    return status;
-}
-
-// Send Idle Power Saver config data to the master OCC
-CmdStatus Status::sendIpsData()
-{
-    CmdStatus status = CmdStatus::FAILURE;
-
-    if (!device.master())
-    {
-        log<level::ERR>(
-            fmt::format(
-                "Status::sendIpsData: SET_CFG_DATA[IPS] does not get sent to slave OCC{}",
-                instance)
-                .c_str());
-        return status;
-    }
-    if (!isPowerVM())
-    {
-        // Idle Power Saver data is only supported on PowerVM systems
-        log<level::DEBUG>(
-            "Status::sendIpsData: SET_CFG_DATA[IPS] does not get sent on non-PowerVM systems");
-        return CmdStatus::SUCCESS;
-    }
-
-    uint8_t enterUtil, exitUtil;
-    uint16_t enterTime, exitTime;
-    const bool ipsEnabled =
-        getIPSParms(enterUtil, enterTime, exitUtil, exitTime);
-
-    log<level::INFO>(
-        fmt::format(
-            "Idle Power Saver Parameters: enabled:{}, enter:{}%/{}s, exit:{}%/{}s",
-            ipsEnabled, enterUtil, enterTime, exitUtil, exitTime)
-            .c_str());
-
-    std::vector<std::uint8_t> cmd, rsp;
-    cmd.reserve(12);
-    cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
-    cmd.push_back(0x00);               // Data Length (2 bytes)
-    cmd.push_back(0x09);               //
-    cmd.push_back(0x11);               // Config Format: IPS Settings
-    cmd.push_back(0x00);               // Version
-    cmd.push_back(ipsEnabled ? 1 : 0); // IPS Enable
-    cmd.push_back(enterTime >> 8);     // Enter Delay Time
-    cmd.push_back(enterTime & 0xFF);   //
-    cmd.push_back(enterUtil);          // Enter Utilization
-    cmd.push_back(exitTime >> 8);      // Exit Delay Time
-    cmd.push_back(exitTime & 0xFF);    //
-    cmd.push_back(exitUtil);           // Exit Utilization
-    log<level::INFO>(fmt::format("Status::sendIpsData: SET_CFG_DATA[IPS] "
-                                 "command to OCC{} ({} bytes)",
-                                 instance, cmd.size())
-                         .c_str());
-    status = occCmd.send(cmd, rsp);
-    if (status == CmdStatus::SUCCESS)
-    {
-        if (rsp.size() == 5)
-        {
-            if (RspStatus::SUCCESS != RspStatus(rsp[2]))
-            {
-                log<level::ERR>(
-                    fmt::format(
-                        "Status::sendIpsData: SET_CFG_DATA[IPS] failed with status 0x{:02X}",
-                        rsp[2])
-                        .c_str());
-                dump_hex(rsp);
-                status = CmdStatus::FAILURE;
-            }
-        }
-        else
-        {
-            log<level::ERR>(
-                "Status::sendIpsData: INVALID SET_CFG_DATA[IPS] response");
-            dump_hex(rsp);
-            status = CmdStatus::FAILURE;
-        }
-    }
-    else
-    {
-        if (status == CmdStatus::OPEN_FAILURE)
-        {
-            log<level::INFO>("Status::sendIpsData: OCC not active yet");
-            status = CmdStatus::SUCCESS;
-        }
-        else
-        {
-            log<level::ERR>("Status::sendIpsData: SET_CFG_DATA[IPS] FAILED!");
-        }
-    }
-
-    return status;
-}
-
 // Send Ambient and Altitude to the OCC
 CmdStatus Status::sendAmbient(const uint8_t inTemp, const uint16_t inAltitude)
 {
diff --git a/occ_status.hpp b/occ_status.hpp
index e8e4530..32041c7 100644
--- a/occ_status.hpp
+++ b/occ_status.hpp
@@ -5,6 +5,7 @@
 #include "occ_command.hpp"
 #include "occ_device.hpp"
 #include "occ_events.hpp"
+#include "powermode.hpp"
 #include "utils.hpp"
 
 #include <org/open_power/Control/Host/server.hpp>
@@ -74,6 +75,9 @@
      *                             protocol
      */
     Status(EventPtr& event, const char* path, Manager& managerRef,
+#ifdef POWER10
+           std::unique_ptr<open_power::occ::powermode::PowerMode>& powerModeRef,
+#endif
            std::function<void(bool)> callBack = nullptr
 #ifdef PLDM
            ,
@@ -84,6 +88,9 @@
         Interface(utils::getBus(), getDbusPath(path).c_str(), true),
         path(path), callBack(callBack), instance(getInstance(path)),
         manager(managerRef),
+#ifdef POWER10
+        pmode(powerModeRef),
+#endif
         device(event,
 #ifdef I2C_OCC
                fs::path(DEV_PATH) / i2c_occ::getI2cDeviceName(path),
@@ -180,16 +187,6 @@
     /** @brief Handle additional tasks when the OCCs reach active state */
     void occsWentActive();
 
-    /** @brief Send mode change command to the master OCC
-     *  @return SUCCESS on success
-     */
-    CmdStatus sendModeChange();
-
-    /** @brief Send Idle Power Saver config data to the master OCC
-     *  @return SUCCESS on success
-     */
-    CmdStatus sendIpsData();
-
     /** @brief Send Ambient & Altitude data to OCC
      *
      *  @param[in] ambient - temperature to send (0xFF will force read
@@ -223,6 +220,11 @@
     /** @brief OCC manager object */
     const Manager& manager;
 
+#ifdef POWER10
+    /** @brief OCC PowerMode object */
+    std::unique_ptr<open_power::occ::powermode::PowerMode>& pmode;
+#endif
+
     /** @brief OCC device object to do bind and unbind */
     Device device;
 
@@ -262,22 +264,6 @@
     }
 
 #ifdef POWER10
-    /** @brief Query the current Hypervisor target
-     * @return true if the current Hypervisor target is PowerVM
-     */
-    bool isPowerVM();
-
-    /** @brief Get the requested power mode property
-     * @return Power mode
-     */
-    SysPwrMode getMode();
-
-    /** @brief Get the Idle Power Saver properties
-     * @return true if IPS is enabled
-     */
-    bool getIPSParms(uint8_t& enterUtil, uint16_t& enterTime, uint8_t& exitUtil,
-                     uint16_t& exitTime);
-
     /**
      * @brief Timer that is started when OCC is detected to be in safe mode
      */
diff --git a/powermode.cpp b/powermode.cpp
index e87b921..04a9787 100644
--- a/powermode.cpp
+++ b/powermode.cpp
@@ -1,10 +1,13 @@
+#include "powermode.hpp"
+
 #include <fmt/core.h>
 
+#include <com/ibm/Host/Target/server.hpp>
 #include <phosphor-logging/log.hpp>
-#include <powermode.hpp>
 #include <xyz/openbmc_project/Control/Power/Mode/server.hpp>
 
 #include <cassert>
+#include <fstream>
 #include <regex>
 
 namespace open_power
@@ -17,13 +20,10 @@
 using namespace phosphor::logging;
 using Mode = sdbusplus::xyz::openbmc_project::Control::Power::server::Mode;
 
+// Called when DBus power mode gets changed
 void PowerMode::modeChanged(sdbusplus::message::message& msg)
 {
-    if (!occStatus.occActive())
-    {
-        // Nothing to do
-        return;
-    }
+    SysPwrMode newMode = SysPwrMode::NO_CHANGE;
 
     std::map<std::string, std::variant<std::string>> properties{};
     std::string interface;
@@ -34,22 +34,51 @@
     {
         auto modeEntryValue = modeEntry->second;
         propVal = std::get<std::string>(modeEntryValue);
-
-        if (convertStringToMode(propVal) != SysPwrMode::NO_CHANGE)
+        newMode = convertStringToMode(propVal);
+        if (newMode != SysPwrMode::NO_CHANGE)
         {
+            // DBus mode changed, get rid of any OEM mode if set
+            persistedData.purge();
+
             log<level::INFO>(
                 fmt::format("Power Mode Change Requested: {}", propVal)
                     .c_str());
 
-            // Trigger mode change to OCC
-            occStatus.sendModeChange();
+            // Send mode change to OCC
+            sendModeChange();
+        }
+    }
+}
+
+// Called from OCC PassThrough interface (via CE login / BMC command line)
+bool PowerMode::setMode(const SysPwrMode newMode, const uint16_t modeData)
+{
+    if (updateDbusMode(newMode) == false)
+    {
+        // Unsupported mode
+        return false;
+    }
+
+    // If new mode is valid customer mode, the DBus update will trigger the mode
+    // change request to OCC.  For OEM modes, the request will be sent here.
+    if (VALID_OEM_POWER_MODE_SETTING(newMode))
+    {
+        // Save OEM mode
+        persistedData.writeModeFile(newMode, modeData);
+
+        // Send mode change to OCC
+        if (sendModeChange() != CmdStatus::SUCCESS)
+        {
+            // Mode change failed
+            return false;
         }
     }
 
-    return;
+    return true;
 }
 
 // Convert PowerMode string to OCC SysPwrMode
+// Returns NO_CHANGE if OEM or unsupported mode
 SysPwrMode convertStringToMode(const std::string& i_modeString)
 {
     SysPwrMode pmode = SysPwrMode::NO_CHANGE;
@@ -65,22 +94,230 @@
     }
     else if (mode == Mode::PowerMode::Static)
     {
-        pmode = SysPwrMode::DISABLE;
+        pmode = SysPwrMode::STATIC;
     }
     else
     {
-        log<level::ERR>(
-            fmt::format("convertStringToMode: Invalid Power Mode specified: {}",
-                        i_modeString)
-                .c_str());
+        if (mode != Mode::PowerMode::OEM)
+        {
+            log<level::ERR>(
+                fmt::format(
+                    "convertStringToMode: Invalid Power Mode specified: {}",
+                    i_modeString)
+                    .c_str());
+        }
     }
 
     return pmode;
 }
 
-void PowerIPS::ipsChanged(sdbusplus::message::message& msg)
+// Check if Hypervisor target is PowerVM
+bool isPowerVM()
 {
-    if (!occStatus.occActive())
+    using namespace open_power::occ::powermode;
+    namespace Hyper = sdbusplus::com::ibm::Host::server;
+    constexpr auto HYPE_PATH = "/com/ibm/host0/hypervisor";
+    constexpr auto HYPE_INTERFACE = "com.ibm.Host.Target";
+    constexpr auto HYPE_PROP = "Target";
+
+    bool powerVmTarget = false;
+
+    // This will throw exception on failure
+    auto& bus = utils::getBus();
+    auto service = utils::getService(HYPE_PATH, HYPE_INTERFACE);
+    auto method = bus.new_method_call(service.c_str(), HYPE_PATH,
+                                      "org.freedesktop.DBus.Properties", "Get");
+    method.append(HYPE_INTERFACE, HYPE_PROP);
+    auto reply = bus.call(method);
+
+    std::variant<std::string> hyperEntryValue;
+    reply.read(hyperEntryValue);
+    auto propVal = std::get<std::string>(hyperEntryValue);
+    if (Hyper::Target::convertHypervisorFromString(propVal) ==
+        Hyper::Target::Hypervisor::PowerVM)
+    {
+        powerVmTarget = true;
+    }
+
+    log<level::DEBUG>(
+        fmt::format("isPowerVM returning {}", powerVmTarget).c_str());
+
+    return powerVmTarget;
+}
+
+// Get the requested power mode from DBus
+SysPwrMode PowerMode::getDbusMode()
+{
+    using namespace open_power::occ::powermode;
+    SysPwrMode currentMode = SysPwrMode::NO_CHANGE;
+
+    // This will throw exception on failure
+    auto& bus = utils::getBus();
+    auto service = utils::getService(PMODE_PATH, PMODE_INTERFACE);
+    auto method = bus.new_method_call(service.c_str(), PMODE_PATH,
+                                      "org.freedesktop.DBus.Properties", "Get");
+    method.append(PMODE_INTERFACE, POWER_MODE_PROP);
+    auto reply = bus.call(method);
+
+    std::variant<std::string> stateEntryValue;
+    reply.read(stateEntryValue);
+    auto propVal = std::get<std::string>(stateEntryValue);
+
+    currentMode = powermode::convertStringToMode(propVal);
+    if (!VALID_POWER_MODE_SETTING(currentMode))
+    {
+        log<level::ERR>(
+            fmt::format(
+                "PowerMode::getDbusMode Invalid power mode found on DBus: {}",
+                currentMode)
+                .c_str());
+        currentMode = SysPwrMode::NO_CHANGE;
+    }
+
+    return currentMode;
+}
+
+// Set the power mode on DBus
+bool PowerMode::updateDbusMode(const SysPwrMode newMode)
+{
+    using namespace open_power::occ::powermode;
+    using namespace std::literals::string_literals;
+
+    if (!VALID_POWER_MODE_SETTING(newMode) &&
+        !VALID_OEM_POWER_MODE_SETTING(newMode))
+    {
+        log<level::ERR>(
+            fmt::format(
+                "PowerMode::updateDbusMode - Requested power mode not supported: {}",
+                newMode)
+                .c_str());
+        return false;
+    }
+
+    // Mode::PowerMode dBusMode;
+    std::string dBusMode;
+    switch (newMode)
+    {
+        case SysPwrMode::STATIC:
+            dBusMode = PMODE_INTERFACE + ".PowerMode.Static"s;
+            break;
+        case SysPwrMode::POWER_SAVING:
+            dBusMode = PMODE_INTERFACE + ".PowerMode.PowerSaving"s;
+            break;
+        case SysPwrMode::MAX_PERF:
+            dBusMode = PMODE_INTERFACE + ".PowerMode.MaximumPerformance"s;
+            break;
+        default:
+            dBusMode = PMODE_INTERFACE + ".PowerMode.OEM"s;
+    }
+
+    utils::setProperty(PMODE_PATH, PMODE_INTERFACE, POWER_MODE_PROP,
+                       std::move(dBusMode));
+
+    return true;
+}
+
+// Send mode change request to the master OCC
+CmdStatus PowerMode::sendModeChange()
+{
+    CmdStatus status = CmdStatus::FAILURE;
+
+    if (!masterActive)
+    {
+        // Nothing to do
+        log<level::DEBUG>("PowerMode::sendModeChange: MODE CHANGE not enabled");
+        return CmdStatus::SUCCESS;
+    }
+
+    if (!isPowerVM())
+    {
+        // Mode change is only supported on PowerVM systems
+        log<level::DEBUG>(
+            "PowerMode::sendModeChange: MODE CHANGE does not get sent on non-PowerVM systems");
+        return CmdStatus::SUCCESS;
+    }
+
+    // Use OEM power mode if it was set
+    SysPwrMode newMode = SysPwrMode::NO_CHANGE;
+    uint16_t modeData = 0;
+    if (persistedData.getOemMode(newMode, modeData) == false)
+    {
+        // Read customer power mode from Dbus
+        newMode = getDbusMode();
+    }
+
+    if (VALID_POWER_MODE_SETTING(newMode) ||
+        VALID_OEM_POWER_MODE_SETTING(newMode))
+    {
+        std::vector<std::uint8_t> cmd, rsp;
+        cmd.reserve(9);
+        cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
+        cmd.push_back(0x00); // Data Length (2 bytes)
+        cmd.push_back(0x06);
+        cmd.push_back(0x30); // Data (Version)
+        cmd.push_back(uint8_t(OccState::NO_CHANGE));
+        cmd.push_back(uint8_t(newMode));
+        cmd.push_back(modeData >> 8);   // Mode Data (Freq Point)
+        cmd.push_back(modeData & 0xFF); //
+        cmd.push_back(0x00);            // reserved
+        log<level::INFO>(
+            fmt::format(
+                "PowerMode::sendModeChange: SET_MODE({},{}) command to OCC{} ({} bytes)",
+                newMode, modeData, occInstance, cmd.size())
+                .c_str());
+        status = occCmd.send(cmd, rsp);
+        if (status == CmdStatus::SUCCESS)
+        {
+            if (rsp.size() == 5)
+            {
+                if (RspStatus::SUCCESS != RspStatus(rsp[2]))
+                {
+                    log<level::ERR>(
+                        fmt::format(
+                            "PowerMode::sendModeChange: SET MODE failed with status 0x{:02X}",
+                            rsp[2])
+                            .c_str());
+                    dump_hex(rsp);
+                    status = CmdStatus::FAILURE;
+                }
+            }
+            else
+            {
+                log<level::ERR>(
+                    "PowerMode::sendModeChange: INVALID SET MODE response");
+                dump_hex(rsp);
+                status = CmdStatus::FAILURE;
+            }
+        }
+        else
+        {
+            if (status == CmdStatus::OPEN_FAILURE)
+            {
+                // OCC not active yet
+                status = CmdStatus::SUCCESS;
+            }
+            else
+            {
+                log<level::ERR>("PowerMode::sendModeChange: SET_MODE FAILED!");
+            }
+        }
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format(
+                "PowerMode::sendModeChange: Unable to set power mode to {}",
+                newMode)
+                .c_str());
+        status = CmdStatus::FAILURE;
+    }
+
+    return status;
+}
+
+void PowerMode::ipsChanged(sdbusplus::message::message& msg)
+{
+    if (!masterActive)
     {
         // Nothing to do
         return;
@@ -145,12 +382,308 @@
     if (parmsChanged)
     {
         // Trigger mode change to OCC
-        occStatus.sendIpsData();
+        sendIpsData();
     }
 
     return;
 }
 
+/** @brief Get the Idle Power Saver properties
+ * @return true if IPS is enabled
+ */
+bool PowerMode::getIPSParms(uint8_t& enterUtil, uint16_t& enterTime,
+                            uint8_t& exitUtil, uint16_t& exitTime)
+{
+    using namespace open_power::occ::powermode;
+    // Defaults:
+    bool ipsEnabled = false; // Disabled
+    enterUtil = 8;           // Enter Utilization (8%)
+    enterTime = 240;         // Enter Delay Time (240s)
+    exitUtil = 12;           // Exit Utilization (12%)
+    exitTime = 10;           // Exit Delay Time (10s)
+
+    std::map<std::string, std::variant<bool, uint8_t, uint64_t>>
+        ipsProperties{};
+
+    // Get all IPS properties from DBus
+    try
+    {
+        auto& bus = utils::getBus();
+        auto service = utils::getService(PIPS_PATH, PIPS_INTERFACE);
+        auto method =
+            bus.new_method_call(service.c_str(), PIPS_PATH,
+                                "org.freedesktop.DBus.Properties", "GetAll");
+        method.append(PIPS_INTERFACE);
+        auto reply = bus.call(method);
+        reply.read(ipsProperties);
+    }
+    catch (const sdbusplus::exception::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "Unable to read Idle Power Saver parameters so it will be disabled: {}",
+                e.what())
+                .c_str());
+        return ipsEnabled;
+    }
+
+    auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP);
+    if (ipsEntry != ipsProperties.end())
+    {
+        ipsEnabled = std::get<bool>(ipsEntry->second);
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("PowerMode::getIPSParms could not find property: {}",
+                        IPS_ENABLED_PROP)
+                .c_str());
+    }
+
+    ipsEntry = ipsProperties.find(IPS_ENTER_UTIL);
+    if (ipsEntry != ipsProperties.end())
+    {
+        enterUtil = std::get<uint8_t>(ipsEntry->second);
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("PowerMode::getIPSParms could not find property: {}",
+                        IPS_ENTER_UTIL)
+                .c_str());
+    }
+
+    ipsEntry = ipsProperties.find(IPS_ENTER_TIME);
+    if (ipsEntry != ipsProperties.end())
+    {
+        std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
+        enterTime =
+            std::chrono::duration_cast<std::chrono::seconds>(ms).count();
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("PowerMode::getIPSParms could not find property: {}",
+                        IPS_ENTER_TIME)
+                .c_str());
+    }
+
+    ipsEntry = ipsProperties.find(IPS_EXIT_UTIL);
+    if (ipsEntry != ipsProperties.end())
+    {
+        exitUtil = std::get<uint8_t>(ipsEntry->second);
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("PowerMode::getIPSParms could not find property: {}",
+                        IPS_EXIT_UTIL)
+                .c_str());
+    }
+
+    ipsEntry = ipsProperties.find(IPS_EXIT_TIME);
+    if (ipsEntry != ipsProperties.end())
+    {
+        std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
+        exitTime = std::chrono::duration_cast<std::chrono::seconds>(ms).count();
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("PowerMode::getIPSParms could not find property: {}",
+                        IPS_EXIT_TIME)
+                .c_str());
+    }
+
+    if (enterUtil > exitUtil)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "ERROR: Idle Power Saver Enter Utilization ({}%) is > Exit Utilization ({}%) - using Exit for both",
+                enterUtil, exitUtil)
+                .c_str());
+        enterUtil = exitUtil;
+    }
+
+    return ipsEnabled;
+}
+
+// Send Idle Power Saver config data to the master OCC
+CmdStatus PowerMode::sendIpsData()
+{
+    CmdStatus status = CmdStatus::FAILURE;
+
+    if (!masterActive)
+    {
+        // Nothing to do
+        return CmdStatus::SUCCESS;
+    }
+
+    if (!isPowerVM())
+    {
+        // Idle Power Saver data is only supported on PowerVM systems
+        log<level::DEBUG>(
+            "PowerMode::sendIpsData: SET_CFG_DATA[IPS] does not get sent on non-PowerVM systems");
+        return CmdStatus::SUCCESS;
+    }
+
+    uint8_t enterUtil, exitUtil;
+    uint16_t enterTime, exitTime;
+    const bool ipsEnabled =
+        getIPSParms(enterUtil, enterTime, exitUtil, exitTime);
+
+    log<level::INFO>(
+        fmt::format(
+            "Idle Power Saver Parameters: enabled:{}, enter:{}%/{}s, exit:{}%/{}s",
+            ipsEnabled, enterUtil, enterTime, exitUtil, exitTime)
+            .c_str());
+
+    std::vector<std::uint8_t> cmd, rsp;
+    cmd.reserve(12);
+    cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
+    cmd.push_back(0x00);               // Data Length (2 bytes)
+    cmd.push_back(0x09);               //
+    cmd.push_back(0x11);               // Config Format: IPS Settings
+    cmd.push_back(0x00);               // Version
+    cmd.push_back(ipsEnabled ? 1 : 0); // IPS Enable
+    cmd.push_back(enterTime >> 8);     // Enter Delay Time
+    cmd.push_back(enterTime & 0xFF);   //
+    cmd.push_back(enterUtil);          // Enter Utilization
+    cmd.push_back(exitTime >> 8);      // Exit Delay Time
+    cmd.push_back(exitTime & 0xFF);    //
+    cmd.push_back(exitUtil);           // Exit Utilization
+    log<level::INFO>(fmt::format("PowerMode::sendIpsData: SET_CFG_DATA[IPS] "
+                                 "command to OCC{} ({} bytes)",
+                                 occInstance, cmd.size())
+                         .c_str());
+    status = occCmd.send(cmd, rsp);
+    if (status == CmdStatus::SUCCESS)
+    {
+        if (rsp.size() == 5)
+        {
+            if (RspStatus::SUCCESS != RspStatus(rsp[2]))
+            {
+                log<level::ERR>(
+                    fmt::format(
+                        "PowerMode::sendIpsData: SET_CFG_DATA[IPS] failed with status 0x{:02X}",
+                        rsp[2])
+                        .c_str());
+                dump_hex(rsp);
+                status = CmdStatus::FAILURE;
+            }
+        }
+        else
+        {
+            log<level::ERR>(
+                "PowerMode::sendIpsData: INVALID SET_CFG_DATA[IPS] response");
+            dump_hex(rsp);
+            status = CmdStatus::FAILURE;
+        }
+    }
+    else
+    {
+        if (status == CmdStatus::OPEN_FAILURE)
+        {
+            // OCC not active yet
+            status = CmdStatus::SUCCESS;
+        }
+        else
+        {
+            log<level::ERR>(
+                "PowerMode::sendIpsData: SET_CFG_DATA[IPS] FAILED!");
+        }
+    }
+
+    return status;
+}
+
+inline void OccPersistData::print()
+{
+    log<level::DEBUG>(
+        fmt::format(
+            "OccPersistData: OEM Mode: 0x{:02X}, OEM Mode Freq: {} (0x{:04X})",
+            oemData.oemMode, oemData.oemModeFreq, oemData.oemModeFreq)
+            .c_str());
+}
+
+// Saves the OEM mode data in the filesystem using cereal.
+void OccPersistData::save()
+{
+    std::filesystem::path opath =
+        std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / oemModeFilename;
+
+    if (!std::filesystem::exists(opath.parent_path()))
+    {
+        std::filesystem::create_directory(opath.parent_path());
+    }
+
+    log<level::DEBUG>(
+        fmt::format("OccPersistData::save: Writing OEM persisted data to {}",
+                    opath.c_str())
+            .c_str());
+    print();
+
+    std::ofstream stream{opath.c_str()};
+    cereal::JSONOutputArchive oarchive{stream};
+
+    oarchive(oemData);
+}
+
+// Loads the OEM mode data in the filesystem using cereal.
+void OccPersistData::load()
+{
+
+    std::filesystem::path ipath =
+        std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / oemModeFilename;
+
+    if (!std::filesystem::exists(ipath))
+    {
+        return;
+    }
+
+    log<level::DEBUG>(
+        fmt::format("OccPersistData::load: Reading OEM persisted data from {}",
+                    ipath.c_str())
+            .c_str());
+    try
+    {
+        std::ifstream stream{ipath.c_str()};
+        cereal::JSONInputArchive iarchive(stream);
+        iarchive(oemData);
+
+        oemSet = true;
+    }
+    catch (const std::exception& e)
+    {
+        auto error = errno;
+        log<level::ERR>(
+            fmt::format("OccPersistData::load: failed to read {}, errno={}",
+                        ipath.c_str(), error)
+                .c_str());
+    }
+
+    print();
+}
+
+void OccPersistData::purge()
+{
+    std::filesystem::path opath =
+        std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / oemModeFilename;
+
+    if (!std::filesystem::exists(opath))
+    {
+        return;
+    }
+
+    print();
+    log<level::DEBUG>("OccPersistData::purge() Removing OEM data");
+
+    oemSet = false;
+    oemData.oemMode = SysPwrMode::NO_CHANGE;
+    oemData.oemModeFreq = 0x0000;
+    remove(opath.c_str());
+}
+
 } // namespace powermode
 
 } // namespace occ
diff --git a/powermode.hpp b/powermode.hpp
index 0d337dc..5342c77 100644
--- a/powermode.hpp
+++ b/powermode.hpp
@@ -3,8 +3,14 @@
 #include "config.h"
 
 #ifdef POWER10
-#include "occ_status.hpp"
+#include "occ_command.hpp"
 
+#include <cereal/archives/json.hpp>
+//#include <cereal/archives/binary.hpp>
+#include <cereal/cereal.hpp>
+#include <cereal/types/string.hpp>
+#include <cereal/types/tuple.hpp>
+#include <cereal/types/vector.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/bus/match.hpp>
 
@@ -14,6 +20,9 @@
 {
 namespace occ
 {
+
+class Manager;
+
 namespace powermode
 {
 
@@ -30,6 +39,11 @@
 constexpr auto IPS_EXIT_UTIL = "ExitUtilizationPercent";
 constexpr auto IPS_EXIT_TIME = "ExitDwellTime";
 
+/** @brief Query the current Hypervisor target
+ * @return true if the current Hypervisor target is PowerVM
+ */
+bool isPowerVM();
+
 /** @brief Convert power mode string to OCC SysPwrMode value
  *
  * @param[in] i_modeString - power mode string
@@ -38,6 +52,93 @@
  */
 SysPwrMode convertStringToMode(const std::string& i_modeString);
 
+struct OemModeData
+{
+    SysPwrMode oemMode = SysPwrMode::NO_CHANGE;
+    uint16_t oemModeFreq = 0x0000;
+
+    /** @brief Function specifying data to archive for cereal.
+     */
+    template <class Archive>
+    void serialize(Archive& archive)
+    {
+        archive(oemMode, oemModeFreq);
+    }
+};
+
+/** @class OccPersistData
+ *  @brief Provides persistent container to store data for OCC
+ *
+ * Data is stored via cereal
+ */
+class OccPersistData
+{
+  public:
+    ~OccPersistData() = default;
+    OccPersistData(const OccPersistData&) = default;
+    OccPersistData& operator=(const OccPersistData&) = default;
+    OccPersistData(OccPersistData&&) = default;
+    OccPersistData& operator=(OccPersistData&&) = default;
+
+    /** @brief Loads any saved OEM mode data */
+    OccPersistData()
+    {
+        load();
+    }
+
+    /** @brief Save Power Mode data to persistent file
+     *
+     *  @param[in] newMode - desired OEM Power Mode
+     *  @param[in] modeData - data required by some OEM Power Modes
+     */
+    void writeModeFile(const SysPwrMode newMode, const uint16_t modeData)
+    {
+        oemData.oemMode = newMode;
+        oemData.oemModeFreq = modeData;
+        oemSet = true;
+        save();
+    }
+
+    /** @brief Return the OEM Power Mode and frequency if enabled
+     *
+     *  @param[out] newMode - OEM mode (if set, else data not changed)
+     *  @param[out] oemFreq - Frequency data for OEM mode
+     *
+     *  @returns true if OEM mode was set
+     */
+    bool getOemMode(SysPwrMode& mode, uint16_t& freq) const
+    {
+        if (!oemSet)
+        {
+            return false;
+        }
+
+        mode = oemData.oemMode;
+        freq = oemData.oemModeFreq;
+        return true;
+    }
+
+    /** @brief Saves the Power Mode data in the filesystem using cereal. */
+    void save();
+
+    /** @brief Removes the OEM mode data. */
+    void purge();
+
+    inline void print();
+
+  private:
+    static constexpr auto oemModeFilename = "oemModeData";
+
+    /** @brief true if an OEM Power Mode was set */
+    bool oemSet = false;
+
+    /** @brief OEM Power Mode data */
+    OemModeData oemData;
+
+    /** @brief Loads the OEM mode data in the filesystem using cereal. */
+    void load();
+};
+
 /** @class PowerMode
  *  @brief Monitors for changes to the power mode and notifies occ
  *
@@ -55,16 +156,67 @@
      * If a change is detected, and the occ is active, then this object will
      * notify the OCC of the change.
      *
-     * @param[in] occStatus - The occ status object
+     * @param[in] managerRef -
+     * @param[in] path -
      */
-    explicit PowerMode(Status& occStatus) :
-        occStatus(occStatus),
+    explicit PowerMode(Manager& managerRef, const char* path) :
+        manager(managerRef), path(path), occInstance(this->path.back() - '0'),
+        occCmd(occInstance, path),
         pmodeMatch(utils::getBus(),
                    sdbusplus::bus::match::rules::propertiesChanged(
                        PMODE_PATH, PMODE_INTERFACE),
-                   [this](auto& msg) { this->modeChanged(msg); }){};
+                   [this](auto& msg) { this->modeChanged(msg); }),
+        ipsMatch(utils::getBus(),
+                 sdbusplus::bus::match::rules::propertiesChanged(
+                     PIPS_PATH, PIPS_INTERFACE),
+                 [this](auto& msg) { this->ipsChanged(msg); }),
+        masterActive(false){};
+
+    bool setMode(const SysPwrMode newMode, const uint16_t modedata);
+
+    /** @brief Send mode change command to the master OCC
+     *  @return SUCCESS on success
+     */
+    CmdStatus sendModeChange();
+
+    /** @brief Send Idle Power Saver config data to the master OCC
+     *  @return SUCCESS on success
+     */
+    CmdStatus sendIpsData();
+
+    /** @brief Notify object of master OCC state.  If not acitve, no
+     * commands will be sent to the master OCC
+     *
+     * @param[in]  isActive - true when master OCC is active
+     */
+    void setMasterActive(const bool isActive = true)
+    {
+        masterActive = isActive;
+    };
 
   private:
+    /** @brief OCC manager object */
+    const Manager& manager;
+
+    /** @brief Pass-through occ path on the bus */
+    std::string path;
+
+    /** @brief OCC instance number */
+    int occInstance;
+
+    /** @brief Object to send commands to the OCC */
+    OccCommand occCmd;
+
+    /** @brief Used to subscribe to dbus pmode property changes **/
+    sdbusplus::bus::match_t pmodeMatch;
+
+    /** @brief Used to subscribe to dbus IPS property changes **/
+    sdbusplus::bus::match_t ipsMatch;
+
+    OccPersistData persistedData;
+
+    bool masterActive;
+
     /** @brief Callback for pmode setting changes
      *
      * Process change and inform OCC
@@ -74,47 +226,33 @@
      */
     void modeChanged(sdbusplus::message::message& msg);
 
-    /* @brief OCC Status object */
-    Status& occStatus;
-
-    /** @brief Used to subscribe to dbus pmode property changes **/
-    sdbusplus::bus::match_t pmodeMatch;
-};
-
-class PowerIPS
-{
-  public:
-    /** @brief PowerIPS object to inform occ of changes to Idle Power Saver
-     * parms
-     *
-     * This object will monitor for changes to the Idle Power Saver settings.
-     * If a change is detected, and the occ is active, then this object will
-     * notify the OCC of the change.
-     *
-     * @param[in] occStatus - The occ status object
+    /** @brief Get the current power mode property from DBus
+     * @return Power mode
      */
-    explicit PowerIPS(Status& occStatus) :
-        occStatus(occStatus),
-        ipsMatch(utils::getBus(),
-                 sdbusplus::bus::match::rules::propertiesChanged(
-                     PIPS_PATH, PIPS_INTERFACE),
-                 [this](auto& msg) { this->ipsChanged(msg); }){};
+    SysPwrMode getDbusMode();
 
-  private:
+    /** @brief Update the power mode property on DBus
+     *
+     * @param[in]  newMode - desired power mode
+     *
+     * @return true on success
+     */
+    bool updateDbusMode(const SysPwrMode newMode);
+
     /** @brief Callback for IPS setting changes
      *
      * Process change and inform OCC
      *
-     * @param[in]  msg       - Data associated with IPS change signal
+     * @param[in]  msg - Data associated with IPS change signal
      *
      */
     void ipsChanged(sdbusplus::message::message& msg);
 
-    /* @brief OCC Status object */
-    Status& occStatus;
-
-    /** @brief Used to subscribe to dbus IPS property changes **/
-    sdbusplus::bus::match_t ipsMatch;
+    /** @brief Get the Idle Power Saver properties from DBus
+     * @return true if IPS is enabled
+     */
+    bool getIPSParms(uint8_t& enterUtil, uint16_t& enterTime, uint8_t& exitUtil,
+                     uint16_t& exitTime);
 };
 
 } // namespace powermode
diff --git a/utils.cpp b/utils.cpp
index 0b983b4..b44d477 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -1,5 +1,7 @@
 #include "utils.hpp"
 
+#include <fmt/core.h>
+
 #include <phosphor-logging/elog-errors.hpp>
 #include <sdbusplus/bus.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
@@ -67,6 +69,41 @@
     return value;
 }
 
+/**
+ * @brief Sets a given object's property value
+ *
+ * @param[in] object - Name of the object containing the property
+ * @param[in] interface - Interface name containing the property
+ * @param[in] property - Property name
+ * @param[in] value - Property value
+ */
+void setProperty(const std::string& objectPath, const std::string& interface,
+                 const std::string& propertyName, std::string&& value)
+{
+    using namespace std::literals::string_literals;
+    std::variant<std::string> varValue(std::forward<std::string>(value));
+
+    auto& bus = getBus();
+    auto service = getService(objectPath, interface);
+    if (service.empty())
+    {
+        return;
+    }
+
+    auto method = bus.new_method_call(service.c_str(), objectPath.c_str(),
+                                      DBUS_PROPERTY_IFACE, "Set");
+    method.append(interface, propertyName, varValue);
+
+    auto reply = bus.call(method);
+    if (reply.is_method_error())
+    {
+        log<level::ERR>(
+            fmt::format("util::setProperty: Failed to set property {}",
+                        propertyName)
+                .c_str());
+    }
+}
+
 std::vector<std::string>
     getSubtreePaths(const std::vector<std::string>& interfaces,
                     const std::string& path)
diff --git a/utils.hpp b/utils.hpp
index 9e6c19b..4a57700 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -54,6 +54,17 @@
                                 const std::string& interface,
                                 const std::string& propertyName);
 
+/**
+ * @brief Sets a given object's property value
+ *
+ * @param[in] object - Name of the object containing the property
+ * @param[in] interface - Interface name containing the property
+ * @param[in] property - Property name
+ * @param[in] value - Property value
+ */
+void setProperty(const std::string& objectPath, const std::string& interface,
+                 const std::string& propertyName, std::string&& value);
+
 /** @brief Get subtree paths
  *
  *  @param[in] interfaces -   D-Bus interfaces