chassis-psu: New Functions for MultiChassis App

Implemented several functions for monitoring power supplies in
multi-chassis systems. Added a new main function, made minor
modification to the PowerSupply class, and added several functions to
the Chassis and ChassisManager classes.

The following is a summary of each object class new addition or modified
functions:

ChassisManager:
  - initChassisPowerMonitoring(): Loops through all the chassis in the
    system and initializes power monitoring process for each chassis's
    PSUs.

Chassis:
 - initPowerMonitoring(): Subscribe to D-Bus power change and initialize
   power monitoring.
 - supportedConfigurationInterfaceAdded(): Handle addition of supported
   configuration and update missing PSUs.
 - psuInterfaceAdded(): Handle addition of PSUs on D-Bus.
 - validatePsuConfigAndInterfacesProcessed(): Validate the PSU
   configuration and reset validation timer if power is on, supported
   configs and have PSUs.
 - analyzeBrownout(): Analyze PSUs for a brownout failure and log error.
 - syncHistory(): Toggles the GPIO to sync power supply input history
   readings.
 - setInputVoltageRating(): Inform each PSUs to set its PSU input.
 - createError(): Create OpenBMC error.
 - hasRequiredPSUs(): TODO
 - updateMissingPSUs(): Update PSU inventory.
 - getSupportedConfiguration(): Retrieve supported configuration from
   D-BUS and matches chassis ID with the  current chassis id to update
   chassis configuration.
 - saveChassisName(): Save chassis short name in the class.
 - powerStateChanged(): Handle for power state property changes.
 - attemptToCreatePowerConfigGPIO(): Attempt to create GPIO

 PowerSupply:
  - PowerSupply(): Added additional class constructors. The constructors
    have the same parameters as the original, except that the new
    constructor include an extra parameter chassis short name.
 - The PowerSupply class functions implementation remains the same as
   the original, except for minor change in
   PowerSupply::setupInputPowerPeakSensor(), where the sensorPath was
   modified to include chassis name.

Test in simulation:
  - Verified supported configuration added, where it polpulates
    supported properties and updates PSUs changes.
  - Validated some of the brownout functionality using fault injection.
  - Validated the PowerSupply class sets the appropriate input voltage
    for the target chassis PSUs.
  - Verified that the chassis name is included in the PSU power input
    peak sensors on D-bus.

Change-Id: I75a1ab1dd004767f072e35f3ce2c83ff281eb1ca
Signed-off-by: Faisal Awada <faisal@us.ibm.com>
diff --git a/phosphor-power-supply/chassis.cpp b/phosphor-power-supply/chassis.cpp
index 85e2a88..4467db5 100644
--- a/phosphor-power-supply/chassis.cpp
+++ b/phosphor-power-supply/chassis.cpp
@@ -2,7 +2,6 @@
 
 #include "chassis.hpp"
 
-#include <filesystem>
 #include <iostream>
 
 using namespace phosphor::logging;
@@ -10,6 +9,8 @@
 namespace phosphor::power::chassis
 {
 
+constexpr auto powerSystemsInputsObjPath =
+    "/xyz/openbmc_project/power/power_supplies/chassis{}/psus";
 constexpr auto IBMCFFPSInterface =
     "xyz.openbmc_project.Configuration.IBMCFFPSConnector";
 constexpr auto chassisIdProp = "SlotNumber";
@@ -25,17 +26,23 @@
 const auto entityMgrService = "xyz.openbmc_project.EntityManager";
 const auto decoratorChassisId = "xyz.openbmc_project.Inventory.Decorator.Slot";
 
+constexpr auto INPUT_HISTORY_SYNC_DELAY = 5;
+
 Chassis::Chassis(sdbusplus::bus_t& bus, const std::string& chassisPath,
                  const sdeventplus::Event& e) :
     bus(bus), chassisPath(chassisPath),
-    chassisPathUniqueId(getChassisPathUniqueId(chassisPath)), eventLoop(e)
+    chassisPathUniqueId(getChassisPathUniqueId(chassisPath)),
+    powerSystemInputs(
+        bus, std::format(powerSystemsInputsObjPath, chassisPathUniqueId)),
+    eventLoop(e)
 {
+    saveChassisName();
     getPSUConfiguration();
+    getSupportedConfiguration();
 }
 
 void Chassis::getPSUConfiguration()
 {
-    namespace fs = std::filesystem;
     auto depth = 0;
 
     try
@@ -49,12 +56,7 @@
         auto connectorsSubTree = getSubTree(bus, "/", IBMCFFPSInterface, depth);
         for (const auto& [path, services] : connectorsSubTree)
         {
-            uint64_t id;
-            fs::path fspath(path);
-            getProperty(decoratorChassisId, chassisIdProp, fspath.parent_path(),
-                        entityMgrService, bus, id);
-            if (id == static_cast<uint64_t>(chassisPathUniqueId))
-
+            if (chassisPathUniqueId == getParentEMUniqueId(bus, path))
             {
                 // For each object in the array of objects, I want
                 // to get properties from the service, path, and
@@ -150,12 +152,10 @@
         lg2::debug(
             "make PowerSupply bus: {I2CBUS} addr: {I2CADDR} presline: {PRESLINE}",
             "I2CBUS", *i2cbus, "I2CADDR", *i2caddr, "PRESLINE", presline);
-        // auto psu = std::make_unique<PowerSupply>(
-        //    bus, invpath, *i2cbus, *i2caddr, driverName, presline,
-        //   std::bind(&Chassis::isPowerOn, this), chassisPath);
+
         auto psu = std::make_unique<PowerSupply>(
             bus, invpath, *i2cbus, *i2caddr, driverName, presline,
-            std::bind(&Chassis::isPowerOn, this));
+            std::bind(&Chassis::isPowerOn, this), chassisShortName);
         psus.emplace_back(std::move(psu));
 
         // Subscribe to power supply presence changes
@@ -178,7 +178,38 @@
 
 void Chassis::getSupportedConfiguration()
 {
-    // TBD
+    try
+    {
+        util::DbusSubtree subtree =
+            util::getSubTree(bus, INVENTORY_OBJ_PATH, supportedConfIntf, 0);
+        if (subtree.empty())
+        {
+            throw std::runtime_error("Supported Configuration Not Found");
+        }
+
+        for (const auto& [objPath, services] : subtree)
+        {
+            std::string service = services.begin()->first;
+            if (objPath.empty() || service.empty())
+            {
+                continue;
+            }
+
+            if (chassisPathUniqueId == getParentEMUniqueId(bus, objPath))
+            {
+                auto properties = util::getAllProperties(
+                    bus, objPath, supportedConfIntf, service);
+                populateSupportedConfiguration(properties);
+                break;
+            }
+        }
+    }
+    catch (const std::exception& e)
+    {
+        // Interface or property not found. Let the Interfaces Added callback
+        // process the information once the interfaces are added to D-Bus.
+        lg2::info("Interface or Property not found, error {ERROR}", "ERROR", e);
+    }
 }
 
 void Chassis::populateSupportedConfiguration(
@@ -319,5 +350,608 @@
     return invalidObjectPathUniqueId;
 }
 
-void Chassis::analyze() {}
+void Chassis::initPowerMonitoring()
+{
+    using namespace sdeventplus;
+
+    validationTimer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
+        eventLoop, std::bind(&Chassis::validateConfig, this));
+    attemptToCreatePowerConfigGPIO();
+
+    // Subscribe to power state changes
+    powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus);
+    powerOnMatch = std::make_unique<sdbusplus::bus::match_t>(
+        bus,
+        sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH,
+                                                        POWER_IFACE),
+        [this](auto& msg) { this->powerStateChanged(msg); });
+    // TODO initialize the chassis
+}
+
+void Chassis::validateConfig()
+{
+    if (!runValidateConfig || supportedConfigs.empty() || psus.empty())
+    {
+        return;
+    }
+
+    for (const auto& psu : psus)
+    {
+        if ((psu->hasInputFault() || psu->hasVINUVFault()) && psu->isPresent())
+        {
+            // Do not try to validate if input voltage fault present.
+            validationTimer->restartOnce(validationTimeout);
+            return;
+        }
+    }
+
+    std::map<std::string, std::string> additionalData;
+    auto supported = hasRequiredPSUs(additionalData);
+    if (supported)
+    {
+        runValidateConfig = false;
+        double actualVoltage;
+        int inputVoltage;
+        int previousInputVoltage = 0;
+        bool voltageMismatch = false;
+
+        for (const auto& psu : psus)
+        {
+            if (!psu->isPresent())
+            {
+                // Only present PSUs report a valid input voltage
+                continue;
+            }
+            psu->getInputVoltage(actualVoltage, inputVoltage);
+            if (previousInputVoltage && inputVoltage &&
+                (previousInputVoltage != inputVoltage))
+            {
+                additionalData["EXPECTED_VOLTAGE"] =
+                    std::to_string(previousInputVoltage);
+                additionalData["ACTUAL_VOLTAGE"] =
+                    std::to_string(actualVoltage);
+                voltageMismatch = true;
+            }
+            if (!previousInputVoltage && inputVoltage)
+            {
+                previousInputVoltage = inputVoltage;
+            }
+        }
+        if (!voltageMismatch)
+        {
+            return;
+        }
+    }
+
+    // Validation failed, create an error log.
+    // Return without setting the runValidateConfig flag to false because
+    // it may be that an additional supported configuration interface is
+    // added and we need to validate it to see if it matches this system.
+    createError("xyz.openbmc_project.Power.PowerSupply.Error.NotSupported",
+                additionalData);
+}
+
+void Chassis::syncHistory()
+{
+    if (driverName != ACBEL_FSG032_DD_NAME)
+    {
+        if (!syncHistoryGPIO)
+        {
+            try
+            {
+                syncHistoryGPIO = createGPIO(INPUT_HISTORY_SYNC_GPIO);
+            }
+            catch (const std::exception& e)
+            {
+                // Not an error, system just hasn't implemented the synch gpio
+                lg2::info("No synchronization GPIO found");
+                syncHistoryGPIO = nullptr;
+            }
+        }
+        if (syncHistoryGPIO)
+        {
+            const std::chrono::milliseconds delay{INPUT_HISTORY_SYNC_DELAY};
+            lg2::info("Synchronize INPUT_HISTORY");
+            syncHistoryGPIO->toggleLowHigh(delay);
+            lg2::info("Synchronize INPUT_HISTORY completed");
+        }
+    }
+
+    // Always clear synch history required after calling this function
+    for (auto& psu : psus)
+    {
+        psu->clearSyncHistoryRequired();
+    }
+}
+
+void Chassis::analyze()
+{
+    auto syncHistoryRequired =
+        std::any_of(psus.begin(), psus.end(), [](const auto& psu) {
+            return psu->isSyncHistoryRequired();
+        });
+    if (syncHistoryRequired)
+    {
+        syncHistory();
+    }
+
+    for (auto& psu : psus)
+    {
+        psu->analyze();
+    }
+
+    analyzeBrownout();
+
+    // Only perform individual PSU analysis if power is on and a brownout has
+    // not already been logged
+    //
+    // Note: TODO Check the chassis state when the power sequencer publishes
+    // chassis power on and system power on
+    if (powerOn && !brownoutLogged)
+    {
+        for (auto& psu : psus)
+        {
+            std::map<std::string, std::string> additionalData;
+
+            if (!psu->isFaultLogged() && !psu->isPresent() &&
+                !validationTimer->isEnabled())
+            {
+                std::map<std::string, std::string> requiredPSUsData;
+                auto requiredPSUsPresent = hasRequiredPSUs(requiredPSUsData);
+                // TODO check required PSU
+
+                if (!requiredPSUsPresent)
+                {
+                    additionalData.merge(requiredPSUsData);
+                    // Create error for power supply missing.
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+                    additionalData["CALLOUT_PRIORITY"] = "H";
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Missing",
+                        additionalData);
+                }
+                psu->setFaultLogged();
+            }
+            else if (!psu->isFaultLogged() && psu->isFaulted())
+            {
+                // Add STATUS_WORD and STATUS_MFR last response, in padded
+                // hexadecimal format.
+                additionalData["STATUS_WORD"] =
+                    std::format("{:#04x}", psu->getStatusWord());
+                additionalData["STATUS_MFR"] =
+                    std::format("{:#02x}", psu->getMFRFault());
+                // If there are faults being reported, they possibly could be
+                // related to a bug in the firmware version running on the power
+                // supply. Capture that data into the error as well.
+                additionalData["FW_VERSION"] = psu->getFWVersion();
+
+                if (psu->hasCommFault())
+                {
+                    additionalData["STATUS_CML"] =
+                        std::format("{:#02x}", psu->getStatusCML());
+                    /* Attempts to communicate with the power supply have
+                     * reached there limit. Create an error. */
+                    additionalData["CALLOUT_DEVICE_PATH"] =
+                        psu->getDevicePath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.CommFault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if ((psu->hasInputFault() || psu->hasVINUVFault()))
+                {
+                    // Include STATUS_INPUT for input faults.
+                    additionalData["STATUS_INPUT"] =
+                        std::format("{:#02x}", psu->getStatusInput());
+
+                    /* The power supply location might be needed if the input
+                     * fault is due to a problem with the power supply itself.
+                     * Include the inventory path with a call out priority of
+                     * low.
+                     */
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+                    additionalData["CALLOUT_PRIORITY"] = "L";
+                    createError("xyz.openbmc_project.Power.PowerSupply.Error."
+                                "InputFault",
+                                additionalData);
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasPSKillFault())
+                {
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.PSKillFault",
+                        additionalData);
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasVoutOVFault())
+                {
+                    // Include STATUS_VOUT for Vout faults.
+                    additionalData["STATUS_VOUT"] =
+                        std::format("{:#02x}", psu->getStatusVout());
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasIoutOCFault())
+                {
+                    // Include STATUS_IOUT for Iout faults.
+                    additionalData["STATUS_IOUT"] =
+                        std::format("{:#02x}", psu->getStatusIout());
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.IoutOCFault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasVoutUVFault() || psu->hasPS12VcsFault() ||
+                         psu->hasPSCS12VFault())
+                {
+                    // Include STATUS_VOUT for Vout faults.
+                    additionalData["STATUS_VOUT"] =
+                        std::format("{:#02x}", psu->getStatusVout());
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                // A fan fault should have priority over a temperature fault,
+                // since a failed fan may lead to a temperature problem.
+                // Only process if not in power fault window.
+                else if (psu->hasFanFault() && !powerFaultOccurring)
+                {
+                    // Include STATUS_TEMPERATURE and STATUS_FANS_1_2
+                    additionalData["STATUS_TEMPERATURE"] =
+                        std::format("{:#02x}", psu->getStatusTemperature());
+                    additionalData["STATUS_FANS_1_2"] =
+                        std::format("{:#02x}", psu->getStatusFans12());
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.FanFault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasTempFault())
+                {
+                    // Include STATUS_TEMPERATURE for temperature faults.
+                    additionalData["STATUS_TEMPERATURE"] =
+                        std::format("{:#02x}", psu->getStatusTemperature());
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                else if (psu->hasMFRFault())
+                {
+                    /* This can represent a variety of faults that result in
+                     * calling out the power supply for replacement: Output
+                     * OverCurrent, Output Under Voltage, and potentially other
+                     * faults.
+                     *
+                     * Also plan on putting specific fault in AdditionalData,
+                     * along with register names and register values
+                     * (STATUS_WORD, STATUS_MFR, etc.).*/
+
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+                // Only process if not in power fault window.
+                else if (psu->hasPgoodFault() && !powerFaultOccurring)
+                {
+                    /* POWER_GOOD# is not low, or OFF is on */
+                    additionalData["CALLOUT_INVENTORY_PATH"] =
+                        psu->getInventoryPath();
+
+                    createError(
+                        "xyz.openbmc_project.Power.PowerSupply.Error.Fault",
+                        additionalData);
+
+                    psu->setFaultLogged();
+                }
+            }
+        }
+    }
+}
+
+void Chassis::analyzeBrownout()
+{
+    // Count number of power supplies failing
+    size_t presentCount = 0;
+    size_t notPresentCount = 0;
+    size_t acFailedCount = 0;
+    size_t pgoodFailedCount = 0;
+    for (const auto& psu : psus)
+    {
+        if (psu->isPresent())
+        {
+            ++presentCount;
+            if (psu->hasACFault())
+            {
+                ++acFailedCount;
+            }
+            else if (psu->hasPgoodFault())
+            {
+                ++pgoodFailedCount;
+            }
+        }
+        else
+        {
+            ++notPresentCount;
+        }
+    }
+
+    // Only issue brownout failure if chassis pgood has failed, it has not
+    // already been logged, at least one PSU has seen an AC fail, and all
+    // present PSUs have an AC or pgood failure. Note an AC fail is only set if
+    // at least one PSU is present.
+    if (powerFaultOccurring && !brownoutLogged && acFailedCount &&
+        (presentCount == (acFailedCount + pgoodFailedCount)))
+    {
+        //  Indicate that the system is in a brownout condition by creating an
+        //  error log and setting the PowerSystemInputs status property to
+        //  Fault.
+        powerSystemInputs.status(
+            sdbusplus::xyz::openbmc_project::State::Decorator::server::
+                PowerSystemInputs::Status::Fault);
+
+        std::map<std::string, std::string> additionalData;
+        additionalData.emplace("NOT_PRESENT_COUNT",
+                               std::to_string(notPresentCount));
+        additionalData.emplace("VIN_FAULT_COUNT",
+                               std::to_string(acFailedCount));
+        additionalData.emplace("PGOOD_FAULT_COUNT",
+                               std::to_string(pgoodFailedCount));
+        lg2::info(
+            "Brownout detected, not present count: {NOT_PRESENT_COUNT}, AC fault count {AC_FAILED_COUNT}, pgood fault count: {PGOOD_FAILED_COUNT}",
+            "NOT_PRESENT_COUNT", notPresentCount, "AC_FAILED_COUNT",
+            acFailedCount, "PGOOD_FAILED_COUNT", pgoodFailedCount);
+
+        createError("xyz.openbmc_project.State.Shutdown.Power.Error.Blackout",
+                    additionalData);
+        brownoutLogged = true;
+    }
+    else
+    {
+        // If a brownout was previously logged but at least one PSU is not
+        // currently in AC fault, determine if the brownout condition can be
+        // cleared
+        if (brownoutLogged && (acFailedCount < presentCount))
+        {
+            // TODO Power State
+        }
+    }
+}
+
+void Chassis::createError(const std::string& faultName,
+                          std::map<std::string, std::string>& additionalData)
+{
+    using namespace sdbusplus::xyz::openbmc_project;
+    constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging";
+    constexpr auto loggingCreateInterface =
+        "xyz.openbmc_project.Logging.Create";
+
+    try
+    {
+        additionalData["_PID"] = std::to_string(getpid());
+
+        auto service =
+            util::getService(loggingObjectPath, loggingCreateInterface, bus);
+
+        if (service.empty())
+        {
+            lg2::error("Unable to get logging manager service");
+            return;
+        }
+
+        auto method = bus.new_method_call(service.c_str(), loggingObjectPath,
+                                          loggingCreateInterface, "Create");
+
+        auto level = Logging::server::Entry::Level::Error;
+        method.append(faultName, level, additionalData);
+
+        auto reply = bus.call(method);
+        // TODO set power supply error
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Failed creating event log for fault {FAULT_NAME} due to error {ERROR}",
+            "FAULT_NAME", faultName, "ERROR", e);
+    }
+}
+
+void Chassis::attemptToCreatePowerConfigGPIO()
+{
+    try
+    {
+        powerConfigGPIO = createGPIO("power-config-full-load");
+    }
+    catch (const std::exception& e)
+    {
+        powerConfigGPIO = nullptr;
+        lg2::info("GPIO not implemented in {CHASSIS}", "CHASSIS",
+                  chassisShortName);
+    }
+}
+
+void Chassis::supportedConfigurationInterfaceAdded(
+    const util::DbusPropertyMap& properties)
+{
+    populateSupportedConfiguration(properties);
+    updateMissingPSUs();
+}
+
+void Chassis::psuInterfaceAdded(util::DbusPropertyMap& properties)
+{
+    getPSUProperties(properties);
+    updateMissingPSUs();
+}
+
+bool Chassis::hasRequiredPSUs(
+    std::map<std::string, std::string>& additionalData)
+{
+    // ignore the following loop so code will compile
+    for (const auto& pair : additionalData)
+    {
+        std::cout << "Key = " << pair.first
+                  << " additionalData value = " << pair.second << "\n";
+    }
+    return true;
+
+    // TODO validate having the required PSUs
+}
+
+void Chassis::updateMissingPSUs()
+{
+    if (supportedConfigs.empty() || psus.empty())
+    {
+        return;
+    }
+
+    // Power supplies default to missing. If the power supply is present,
+    // the PowerSupply object will update the inventory Present property to
+    // true. If we have less than the required number of power supplies, and
+    // this power supply is missing, update the inventory Present property
+    // to false to indicate required power supply is missing. Avoid
+    // indicating power supply missing if not required.
+
+    auto presentCount =
+        std::count_if(psus.begin(), psus.end(),
+                      [](const auto& psu) { return psu->isPresent(); });
+
+    for (const auto& config : supportedConfigs)
+    {
+        for (const auto& psu : psus)
+        {
+            auto psuModel = psu->getModelName();
+            auto psuShortName = psu->getShortName();
+            auto psuInventoryPath = psu->getInventoryPath();
+            auto relativeInvPath =
+                psuInventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+            auto psuPresent = psu->isPresent();
+            auto presProperty = false;
+            auto propReadFail = false;
+
+            try
+            {
+                presProperty = getPresence(bus, psuInventoryPath);
+                propReadFail = false;
+            }
+            catch (const sdbusplus::exception_t& e)
+            {
+                propReadFail = true;
+                // Relying on property change or interface added to retry.
+                // Log an informational trace to the journal.
+                lg2::info(
+                    "D-Bus property {PSU_INVENTORY_PATH} access failure exception",
+                    "PSU_INVENTORY_PATH", psuInventoryPath);
+            }
+
+            if (psuModel.empty())
+            {
+                if (!propReadFail && (presProperty != psuPresent))
+                {
+                    // We already have this property, and it is not false
+                    // set Present to false
+                    setPresence(bus, relativeInvPath, psuPresent, psuShortName);
+                }
+                continue;
+            }
+
+            if (config.first != psuModel)
+            {
+                continue;
+            }
+
+            if ((presentCount < config.second.powerSupplyCount) && !psuPresent)
+            {
+                setPresence(bus, relativeInvPath, psuPresent, psuShortName);
+            }
+        }
+    }
+}
+
+void Chassis::powerStateChanged(sdbusplus::message_t& msg)
+{
+    std::string msgSensor;
+    std::map<std::string, std::variant<int>> msgData;
+    msg.read(msgSensor, msgData);
+
+    // Check if it was the state property that changed.
+    auto valPropMap = msgData.find("state");
+    if (valPropMap != msgData.end())
+    {
+        int state = std::get<int>(valPropMap->second);
+        if (state)
+        {
+            // Power on requested
+            powerOn = true;
+            powerFaultOccurring = false;
+            validationTimer->restartOnce(validationTimeout);
+            // TODO clear faults
+
+            syncHistory();
+            // TODO set power config
+
+            setInputVoltageRating();
+        }
+        else
+        {
+            // Power off requested
+            powerOn = false;
+            powerFaultOccurring = false;
+            runValidateConfig = true;
+        }
+    }
+
+    // Check if it was the pgood property that changed.
+    valPropMap = msgData.find("pgood");
+    if (valPropMap != msgData.end())
+    {
+        int pgood = std::get<int>(valPropMap->second);
+        if (!pgood)
+        {
+            // Chassis power good has turned off
+            if (powerOn)
+            {
+                // pgood is off but state is on, in power fault window
+                powerFaultOccurring = true;
+            }
+        }
+    }
+    lg2::info(
+        "powerStateChanged: power on: {POWER_ON}, power fault occurring: {POWER_FAULT_OCCURRING}",
+        "POWER_ON", powerOn, "POWER_FAULT_OCCURRING", powerFaultOccurring);
+}
+
 } // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/chassis.hpp b/phosphor-power-supply/chassis.hpp
index e976921..6471f26 100644
--- a/phosphor-power-supply/chassis.hpp
+++ b/phosphor-power-supply/chassis.hpp
@@ -1,6 +1,5 @@
 #pragma once
-
-#include "power_supply.hpp"
+#include "new_power_supply.hpp"
 #include "types.hpp"
 #include "utility.hpp"
 
@@ -13,6 +12,7 @@
 #include <sdeventplus/utility/timer.hpp>
 #include <xyz/openbmc_project/State/Decorator/PowerSystemInputs/server.hpp>
 
+#include <filesystem>
 #include <map>
 #include <string>
 #include <vector>
@@ -25,6 +25,8 @@
 };
 
 using namespace phosphor::power::psu;
+using namespace phosphor::power::util;
+using namespace sdeventplus;
 
 namespace phosphor::power::chassis
 {
@@ -42,6 +44,18 @@
 constexpr auto validationTimeout = std::chrono::seconds(30);
 
 /**
+ * @class PowerSystemInputs
+ * @brief A concrete implementation for the PowerSystemInputs interface.
+ */
+class PowerSystemInputs : public PowerSystemInputsObject
+{
+  public:
+    PowerSystemInputs(sdbusplus::bus_t& bus, const std::string& path) :
+        PowerSystemInputsObject(bus, path.c_str())
+    {}
+};
+
+/**
  * @class Chassis
  *
  * @brief This class will create an object used to manage and monitor a list of
@@ -91,6 +105,47 @@
         return powerOn;
     }
 
+    /**
+     * @brief Initialize power monitoring infrastructure for Chassis.
+     * Sets up configuration validation timer, attempts to create GPIO,
+     * subscribe to D-Bus power state change events.
+     */
+    void initPowerMonitoring();
+
+    /**
+     * @brief Handles addition of the SupportedConfiguration interface.
+     * This function triggered when the SupportedConfiguration interface added
+     * to a D-Bus object. The function  calls populateSupportedConfiguration()
+     * and updateMissingPSUs() to processes the provided properties.
+     *
+     * @param properties A map of D-Bus properties associated with the
+     * SupportedConfiguration interface.
+     */
+    void supportedConfigurationInterfaceAdded(
+        const util::DbusPropertyMap& properties);
+
+    /**
+     * @brief Handle the addition of PSU interface.
+     * This function is called when a Power Supply interface added to a D-Bus.
+     * This function calls getPSUProperties() and updateMissingPSUs().
+     *
+     * @param properties A map of D-Bus properties for the PSU interface.
+     */
+    void psuInterfaceAdded(util::DbusPropertyMap& properties);
+
+    /**
+     * @brief Call to validate the psu configuration if the power is on and both
+     * the IBMCFFPSConnector and SupportedConfiguration interfaces have been
+     * processed
+     */
+    void validatePsuConfigAndInterfacesProcessed()
+    {
+        if (powerOn && !psus.empty() && !supportedConfigs.empty())
+        {
+            validationTimer->restartOnce(validationTimeout);
+        }
+    };
+
   private:
     /**
      * @brief The D-Bus object
@@ -108,6 +163,20 @@
     /** @brief True if the power is on. */
     bool powerOn = false;
 
+    /** @brief True if power control is in the window between chassis pgood loss
+     * and power off.
+     */
+    bool powerFaultOccurring = false;
+
+    /** @brief True if an error for a brownout has already been logged. */
+    bool brownoutLogged = false;
+
+    /** @brief Used as part of subscribing to power on state changes*/
+    std::string powerService;
+
+    /** @brief Used to subscribe to D-Bus power on state changes */
+    std::unique_ptr<sdbusplus::bus::match_t> powerOnMatch;
+
     /** @brief Used to subscribe to D-Bus power supply presence changes */
     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> presenceMatches;
 
@@ -136,22 +205,44 @@
     std::string driverName;
 
     /**
+     * @brief The libgpiod object for setting the power supply config
+     */
+    std::unique_ptr<GPIOInterfaceBase> powerConfigGPIO = nullptr;
+
+    /**
      * @brief Chassis D-Bus object path
      */
     std::string chassisPath;
 
     /**
+     * @brief Chassis name;
+     */
+    std::string chassisShortName;
+
+    /**
      * @brief The Chassis path unique ID
+     *
+     * Note: chassisPathUniqueId must be declared before powerSystemInputs.
      */
     uint64_t chassisPathUniqueId = invalidObjectPathUniqueId;
 
     /**
+     * @brief PowerSystemInputs object
+     */
+    PowerSystemInputs powerSystemInputs;
+
+    /**
      * @brief Declares a constant reference to an sdeventplus::Event to manage
      * async processing.
      */
     const sdeventplus::Event& eventLoop;
 
     /**
+     * @brief GPIO to toggle to 'sync' power supply input history.
+     */
+    std::unique_ptr<GPIOInterfaceBase> syncHistoryGPIO = nullptr;
+
+    /**
      * @brief Get PSU properties from D-Bus, use that to build a power supply
      * object.
      *
@@ -165,9 +256,9 @@
     void getPSUConfiguration();
 
     /**
-     * @brief Initialize the chassis's supported configuration from the
-     * Supported Configuration D-Bus object provided by the Entity
-     * Manager.
+     * @brief Queries D-Bus for chassis configuration provided by the Entity
+     * Manager. Matches the object against the current chassis unique ID. Upon
+     * finding a match calls populateSupportedConfiguration().
      */
     void getSupportedConfiguration();
 
@@ -209,6 +300,99 @@
      * @return uint64_t - Chassis path unique ID.
      */
     uint64_t getChassisPathUniqueId(const std::string& path);
+
+    /**
+     * @brief Initializes the chassis.
+     *
+     */
+    void initialize() {}; // TODO
+
+    /**
+     * @brief Perform power supply configuration validation.
+     * @details Validates if the existing power supply properties are a
+     * supported configuration, and acts on its findings such as logging
+     * errors.
+     */
+    void validateConfig();
+
+    /**
+     * @brief Analyze the set of the power supplies for a brownout failure. Log
+     * error when necessary, clear brownout condition when window has passed.
+     */
+    void analyzeBrownout();
+
+    /**
+     * @brief Toggles the GPIO to sync power supply input history readings
+     * @details This GPIO is connected to all supplies.  This will clear the
+     * previous readings out of the supplies and restart them both at the
+     * same time zero and at record ID 0.  The supplies will return 0
+     * bytes of data for the input history command right after this until
+     * a new entry shows up.
+     *
+     * This will cause the code to delete all previous history data and
+     * start fresh.
+     */
+    void syncHistory();
+
+    /**
+     * @brief Tells each PSU to set its power supply input
+     * voltage rating D-Bus property.
+     */
+    inline void setInputVoltageRating()
+    {
+        for (auto& psu : psus)
+        {
+            psu->setInputVoltageRating();
+        }
+    }
+
+    /**
+     * Create an error
+     *
+     * @param[in] faultName - 'name' message for the BMC error log entry
+     * @param[in,out] additionalData - The AdditionalData property for the error
+     */
+    void createError(const std::string& faultName,
+                     std::map<std::string, std::string>& additionalData);
+
+    /**
+     * @brief Check that all PSUs have the same model name and that the system
+     * has the required number of PSUs present as specified in the Supported
+     * Configuration interface.
+     *
+     * @param[out] additionalData - Contains debug information on why the check
+     *             might have failed. Can be used to fill in error logs.
+     * @return true if all the required PSUs are present, false otherwise.
+     */
+    bool hasRequiredPSUs(std::map<std::string, std::string>& additionalData);
+
+    /**
+     * @brief Update inventory for missing required power supplies
+     */
+    void updateMissingPSUs();
+
+    /**
+     * @brief Assign chassis short name.
+     */
+    void saveChassisName()
+    {
+        std::filesystem::path path(chassisPath);
+        chassisShortName = path.filename();
+    }
+
+    /**
+     * @brief Callback for power state property changes
+     *
+     * Process changes to the powered on state property for the chassis.
+     *
+     * @param[in] msg - Data associated with the power state signal
+     */
+    void powerStateChanged(sdbusplus::message_t& msg);
+
+    /**
+     * @breif Attempt to create GPIO
+     */
+    void attemptToCreatePowerConfigGPIO();
 };
 
 } // namespace phosphor::power::chassis
diff --git a/phosphor-power-supply/chassis_manager.cpp b/phosphor-power-supply/chassis_manager.cpp
index 710b5dc..db16ac2 100644
--- a/phosphor-power-supply/chassis_manager.cpp
+++ b/phosphor-power-supply/chassis_manager.cpp
@@ -3,6 +3,7 @@
 #include "chassis_manager.hpp"
 
 #include <phosphor-logging/lg2.hpp>
+
 using namespace phosphor::logging;
 
 namespace phosphor::power::chassis_manager
@@ -40,6 +41,7 @@
     auto interval = std::chrono::milliseconds(1000);
     timer = std::make_unique<utility::Timer<ClockId::Monotonic>>(
         e, std::bind(&ChassisManager::analyze, this), interval);
+    initChassisPowerMonitoring();
 }
 
 void ChassisManager::entityManagerIfaceAdded(sdbusplus::message_t& msg)
@@ -65,9 +67,8 @@
             {
                 lg2::debug("InterfacesAdded for: {SUPPORTED_CONFIGURATION}",
                            "SUPPORTED_CONFIGURATION", supportedConfIntf);
-                // Future implementation
-                // chassisMatchPtr->supportedConfigurationInterfaceAdded(
-                //     itInterface->second);
+                chassisMatchPtr->supportedConfigurationInterfaceAdded(
+                    itInterface->second);
             }
         }
         itInterface = interfaces.find(IBMCFFPSInterface);
@@ -81,16 +82,14 @@
             {
                 lg2::info("InterfacesAdded for: {IBMCFFPSINTERFACE}",
                           "IBMCFFPSINTERFACE", IBMCFFPSInterface);
-                // Future implementation
-                // chassisMatchPtr->psuInterfaceAdded(itInterface->second);
+                chassisMatchPtr->psuInterfaceAdded(itInterface->second);
             }
         }
         if (chassisMatchPtr != nullptr)
         {
             lg2::debug(
                 "InterfacesAdded validatePsuConfigAndInterfacesProcessed()");
-            // Future implementation
-            // chassisMatchPtr->validatePsuConfigAndInterfacesProcessed();
+            chassisMatchPtr->validatePsuConfigAndInterfacesProcessed();
         }
     }
     catch (const std::exception& e)
@@ -142,4 +141,13 @@
                    e);
     }
 }
+
+void ChassisManager::initChassisPowerMonitoring()
+{
+    for (const auto& chassis : listOfChassis)
+    {
+        chassis->initPowerMonitoring();
+    }
+}
+
 } // namespace phosphor::power::chassis_manager
diff --git a/phosphor-power-supply/chassis_manager.hpp b/phosphor-power-supply/chassis_manager.hpp
index 100514d..cccd8db 100644
--- a/phosphor-power-supply/chassis_manager.hpp
+++ b/phosphor-power-supply/chassis_manager.hpp
@@ -122,6 +122,11 @@
      */
     phosphor::power::chassis::Chassis* getMatchingChassisPtr(
         uint64_t chassisId);
+
+    /**
+     * @brief Initialize chassis power monitoring.
+     */
+    void initChassisPowerMonitoring();
 };
 
 } // namespace phosphor::power::chassis_manager
diff --git a/phosphor-power-supply/new_main.cpp b/phosphor-power-supply/new_main.cpp
new file mode 100644
index 0000000..638f971
--- /dev/null
+++ b/phosphor-power-supply/new_main.cpp
@@ -0,0 +1,28 @@
+#include "chassis_manager.hpp"
+
+#include <CLI/CLI.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdeventplus/event.hpp>
+
+#include <filesystem>
+
+using namespace phosphor::power;
+
+int main()
+{
+    using namespace phosphor::logging;
+
+    CLI::App app{"OpenBMC Power Supply Unit Monitor"};
+
+    auto bus = sdbusplus::bus::new_default();
+    auto event = sdeventplus::Event::get_default();
+
+    // Attach the event object to the bus object so we can
+    // handle both sd_events (for the timers) and dbus signals.
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+    chassis_manager::ChassisManager chassis_manager(bus, event);
+
+    return chassis_manager.run();
+}
diff --git a/phosphor-power-supply/new_power_supply.cpp b/phosphor-power-supply/new_power_supply.cpp
new file mode 100644
index 0000000..b3ba1c7
--- /dev/null
+++ b/phosphor-power-supply/new_power_supply.cpp
@@ -0,0 +1,1346 @@
+#include "config.h"
+
+#include "new_power_supply.hpp"
+
+#include "types.hpp"
+#include "util.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/Device/error.hpp>
+
+#include <chrono>  // sleep_for()
+#include <cmath>
+#include <cstdint> // uint8_t...
+#include <format>
+#include <fstream>
+#include <regex>
+#include <thread> // sleep_for()
+
+namespace phosphor::power::psu
+{
+// Amount of time in milliseconds to delay between power supply going from
+// missing to present before running the bind command(s).
+constexpr auto bindDelay = 1000;
+
+using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
+
+PowerSupply::PowerSupply(
+    sdbusplus::bus_t& bus, const std::string& invpath, std::uint8_t i2cbus,
+    std::uint16_t i2caddr, const std::string& driver,
+    const std::string& gpioLineName, std::function<bool()>&& callback,
+    const std::string& chassisShortName) :
+    bus(bus), inventoryPath(invpath),
+    bindPath("/sys/bus/i2c/drivers/" + driver), isPowerOn(std::move(callback)),
+    driverName(driver), chassisName(chassisShortName)
+{
+    if (inventoryPath.empty())
+    {
+        throw std::invalid_argument{"Invalid empty inventoryPath"};
+    }
+
+    if (gpioLineName.empty())
+    {
+        throw std::invalid_argument{"Invalid empty gpioLineName"};
+    }
+
+    shortName = findShortName(inventoryPath);
+
+    lg2::debug("{SHORT_NAME} gpioLineName: {GPIO_LINE_NAME}", "SHORT_NAME",
+               shortName, "GPIO_LINE_NAME", gpioLineName);
+    presenceGPIO = createGPIO(gpioLineName);
+
+    std::ostringstream ss;
+    ss << std::hex << std::setw(4) << std::setfill('0') << i2caddr;
+    std::string addrStr = ss.str();
+    std::string busStr = std::to_string(i2cbus);
+    bindDevice = busStr;
+    bindDevice.append("-");
+    bindDevice.append(addrStr);
+
+    pmbusIntf = phosphor::pmbus::createPMBus(i2cbus, addrStr);
+
+    // Get the current state of the Present property.
+    try
+    {
+        updatePresenceGPIO();
+    }
+    catch (...)
+    {
+        // If the above attempt to use the GPIO failed, it likely means that the
+        // GPIOs are in use by the kernel, meaning it is using gpio-keys.
+        // So, I should rely on phosphor-gpio-presence to update D-Bus, and
+        // work that way for power supply presence.
+        presenceGPIO = nullptr;
+        // Setup the functions to call when the D-Bus inventory path for the
+        // Present property changes.
+        presentMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::propertiesChanged(inventoryPath,
+                                                            INVENTORY_IFACE),
+            [this](auto& msg) { this->inventoryChanged(msg); });
+
+        presentAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::interfacesAdded() +
+                sdbusplus::bus::match::rules::argNpath(0, inventoryPath),
+            [this](auto& msg) { this->inventoryAdded(msg); });
+
+        updatePresence();
+        updateInventory();
+        setupSensors();
+    }
+    try
+    {
+        setInputVoltageRating();
+    }
+    catch (const std::exception& e)
+    {
+        lg2::info("setInputVoltageRating exception: {ERR}", "ERR", e);
+    }
+}
+
+PowerSupply::PowerSupply(
+    sdbusplus::bus_t& bus, const std::string& invpath, std::uint8_t i2cbus,
+    std::uint16_t i2caddr, const std::string& driver,
+    const std::string& gpioLineName, std::function<bool()>&& callback) :
+    PowerSupply(bus, invpath, i2cbus, i2caddr, driver, gpioLineName,
+                std::move(callback), "")
+{}
+
+void PowerSupply::bindOrUnbindDriver(bool present)
+{
+    // Symbolic link to the device will exist if the driver is bound.
+    // So exit no action required if both the link and PSU are present
+    // or neither is present.
+    namespace fs = std::filesystem;
+    fs::path path;
+    auto action = (present) ? "bind" : "unbind";
+
+    // This case should not happen, if no device driver name return.
+    if (driverName.empty())
+    {
+        lg2::info("No device driver name found");
+        return;
+    }
+    if (bindPath.string().find(driverName) != std::string::npos)
+    {
+        // bindPath has driver name
+        path = bindPath / action;
+    }
+    else
+    {
+        // Add driver name to bindPath
+        path = bindPath / driverName / action;
+        bindPath = bindPath / driverName;
+    }
+
+    if ((std::filesystem::exists(bindPath / bindDevice) && present) ||
+        (!std::filesystem::exists(bindPath / bindDevice) && !present))
+    {
+        return;
+    }
+    if (present)
+    {
+        std::this_thread::sleep_for(std::chrono::milliseconds(bindDelay));
+        lg2::info("Binding device driver. path: {PATH} device: {BIND_DEVICE}",
+                  "PATH", path, "BIND_DEVICE", bindDevice);
+    }
+    else
+    {
+        lg2::info("Unbinding device driver. path: {PATH} device: {BIND_DEVICE}",
+                  "PATH", path, "BIND_DEVICE", bindDevice);
+    }
+
+    std::ofstream file;
+
+    file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
+                    std::ofstream::eofbit);
+
+    try
+    {
+        file.open(path);
+        file << bindDevice;
+        file.close();
+    }
+    catch (const std::exception& e)
+    {
+        auto err = errno;
+
+        lg2::error("Failed binding or unbinding device. errno={ERRNO}", "ERRNO",
+                   err);
+    }
+}
+
+void PowerSupply::updatePresence()
+{
+    try
+    {
+        present = getPresence(bus, inventoryPath);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        // Relying on property change or interface added to retry.
+        // Log an informational trace to the journal.
+        lg2::info("D-Bus property {INVENTORY_PATH} access failure exception",
+                  "INVENTORY_PATH", inventoryPath);
+    }
+}
+
+void PowerSupply::updatePresenceGPIO()
+{
+    bool presentOld = present;
+
+    try
+    {
+        if (presenceGPIO->read() > 0)
+        {
+            present = true;
+        }
+        else
+        {
+            present = false;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("presenceGPIO read fail: {ERROR}", "ERROR", e);
+        throw;
+    }
+
+    if (presentOld != present)
+    {
+        lg2::debug("{SHORT_NAME} presentOld: {PRESENT_OLD} present: {PRESENT}",
+                   "SHORT_NAME", shortName, "PRESENT_OLD", presentOld,
+                   "PRESENT", present);
+
+        auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+
+        bindOrUnbindDriver(present);
+        if (present)
+        {
+            // If the power supply was present, then missing, and present again,
+            // the hwmon path may have changed. We will need the correct/updated
+            // path before any reads or writes are attempted.
+            pmbusIntf->findHwmonDir();
+        }
+
+        setPresence(bus, invpath, present, shortName);
+        setupSensors();
+        updateInventory();
+
+        // Need Functional to already be correct before calling this.
+        checkAvailability();
+
+        if (present)
+        {
+            onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
+            clearFaults();
+            // Indicate that the input history data and timestamps between all
+            // the power supplies that are present in the system need to be
+            // synchronized.
+            syncHistoryRequired = true;
+        }
+        else
+        {
+            setSensorsNotAvailable();
+        }
+    }
+}
+
+void PowerSupply::analyzeCMLFault()
+{
+    if (statusWord & phosphor::pmbus::status_word::CML_FAULT)
+    {
+        if (cmlFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error(
+                    "{SHORT_NAME} CML fault: STATUS_WORD = {STATUS_WORD}, "
+                    "STATUS_CML = {STATUS_CML}",
+                    "SHORT_NAME", shortName, "STATUS_WORD",
+                    lg2::hex | lg2::field16, statusWord, "STATUS_CML",
+                    lg2::hex | lg2::field8, statusCML);
+            }
+            cmlFault++;
+        }
+    }
+    else
+    {
+        cmlFault = 0;
+    }
+}
+
+void PowerSupply::analyzeInputFault()
+{
+    if (statusWord & phosphor::pmbus::status_word::INPUT_FAULT_WARN)
+    {
+        if (inputFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error(
+                    "{SHORT_NAME} INPUT fault: STATUS_WORD = {STATUS_WORD}, "
+                    "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+                    "STATUS_INPUT = {STATUS_INPUT}",
+                    "SHORT_NAME", shortName, "STATUS_WORD",
+                    lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+                    lg2::hex | lg2::field8, statusMFR, "STATUS_INPUT",
+                    lg2::hex | lg2::field8, statusInput);
+            }
+            inputFault++;
+        }
+    }
+
+    // If had INPUT/VIN_UV fault, and now off.
+    // Trace that odd behavior.
+    if (inputFault &&
+        !(statusWord & phosphor::pmbus::status_word::INPUT_FAULT_WARN))
+    {
+        lg2::info(
+            "{SHORT_NAME} INPUT fault cleared: STATUS_WORD = {STATUS_WORD}, "
+            "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+            "STATUS_INPUT = {STATUS_INPUT}",
+            "SHORT_NAME", shortName, "STATUS_WORD", lg2::hex | lg2::field16,
+            statusWord, "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+            statusMFR, "STATUS_INPUT", lg2::hex | lg2::field8, statusInput);
+        inputFault = 0;
+    }
+}
+
+void PowerSupply::analyzeVoutOVFault()
+{
+    if (statusWord & phosphor::pmbus::status_word::VOUT_OV_FAULT)
+    {
+        if (voutOVFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error(
+                    "{SHORT_NAME} VOUT_OV_FAULT fault: STATUS_WORD = {STATUS_WORD}, "
+                    "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+                    "STATUS_VOUT = {STATUS_VOUT}",
+                    "SHORT_NAME", shortName, "STATUS_WORD",
+                    lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+                    lg2::hex | lg2::field8, statusMFR, "STATUS_VOUT",
+                    lg2::hex | lg2::field8, statusVout);
+            }
+
+            voutOVFault++;
+        }
+    }
+    else
+    {
+        voutOVFault = 0;
+    }
+}
+
+void PowerSupply::analyzeIoutOCFault()
+{
+    if (statusWord & phosphor::pmbus::status_word::IOUT_OC_FAULT)
+    {
+        if (ioutOCFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error(
+                    "{SHORT_NAME} IOUT fault: STATUS_WORD = {STATUS_WORD}, "
+                    "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+                    "STATUS_IOUT = {STATUS_IOUT}",
+                    "SHORT_NAME", shortName, "STATUS_WORD",
+                    lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+                    lg2::hex | lg2::field8, statusMFR, "STATUS_IOUT",
+                    lg2::hex | lg2::field8, statusIout);
+            }
+
+            ioutOCFault++;
+        }
+    }
+    else
+    {
+        ioutOCFault = 0;
+    }
+}
+
+void PowerSupply::analyzeVoutUVFault()
+{
+    if ((statusWord & phosphor::pmbus::status_word::VOUT_FAULT) &&
+        !(statusWord & phosphor::pmbus::status_word::VOUT_OV_FAULT))
+    {
+        if (voutUVFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error(
+                    "{SHORT_NAME} VOUT_UV_FAULT fault: STATUS_WORD = {STATUS_WORD}, "
+                    "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+                    "STATUS_VOUT = {STATUS_VOUT}",
+                    "SHORT_NAME", shortName, "STATUS_WORD",
+                    lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+                    lg2::hex | lg2::field8, statusMFR, "STATUS_VOUT",
+                    lg2::hex | lg2::field8, statusVout);
+            }
+            voutUVFault++;
+        }
+    }
+    else
+    {
+        voutUVFault = 0;
+    }
+}
+
+void PowerSupply::analyzeFanFault()
+{
+    if (statusWord & phosphor::pmbus::status_word::FAN_FAULT)
+    {
+        if (fanFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error("{SHORT_NAME} FANS fault/warning: "
+                           "STATUS_WORD = {STATUS_WORD}, "
+                           "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+                           "STATUS_FANS_1_2 = {STATUS_FANS_1_2}",
+                           "SHORT_NAME", shortName, "STATUS_WORD",
+                           lg2::hex | lg2::field16, statusWord,
+                           "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+                           statusMFR, "STATUS_FANS_1_2", lg2::hex | lg2::field8,
+                           statusFans12);
+            }
+            fanFault++;
+        }
+    }
+    else
+    {
+        fanFault = 0;
+    }
+}
+
+void PowerSupply::analyzeTemperatureFault()
+{
+    if (statusWord & phosphor::pmbus::status_word::TEMPERATURE_FAULT_WARN)
+    {
+        if (tempFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error("{SHORT_NAME} TEMPERATURE fault/warning: "
+                           "STATUS_WORD = {STATUS_WORD}, "
+                           "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+                           "STATUS_TEMPERATURE = {STATUS_TEMPERATURE}",
+                           "SHORT_NAME", shortName, "STATUS_WORD",
+                           lg2::hex | lg2::field16, statusWord,
+                           "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+                           statusMFR, "STATUS_TEMPERATURE",
+                           lg2::hex | lg2::field8, statusTemperature);
+            }
+            tempFault++;
+        }
+    }
+    else
+    {
+        tempFault = 0;
+    }
+}
+
+void PowerSupply::analyzePgoodFault()
+{
+    if ((statusWord & phosphor::pmbus::status_word::POWER_GOOD_NEGATED) ||
+        (statusWord & phosphor::pmbus::status_word::UNIT_IS_OFF))
+    {
+        if (pgoodFault < PGOOD_DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error("{SHORT_NAME} PGOOD fault: "
+                           "STATUS_WORD = {STATUS_WORD}, "
+                           "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}",
+                           "SHORT_NAME", shortName, "STATUS_WORD",
+                           lg2::hex | lg2::field16, statusWord,
+                           "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+                           statusMFR);
+            }
+            pgoodFault++;
+        }
+    }
+    else
+    {
+        pgoodFault = 0;
+    }
+}
+
+void PowerSupply::determineMFRFault()
+{
+    if (bindPath.string().find(IBMCFFPS_DD_NAME) != std::string::npos)
+    {
+        // IBM MFR_SPECIFIC[4] is PS_Kill fault
+        if (statusMFR & 0x10)
+        {
+            if (psKillFault < DEGLITCH_LIMIT)
+            {
+                psKillFault++;
+            }
+        }
+        else
+        {
+            psKillFault = 0;
+        }
+        // IBM MFR_SPECIFIC[6] is 12Vcs fault.
+        if (statusMFR & 0x40)
+        {
+            if (ps12VcsFault < DEGLITCH_LIMIT)
+            {
+                ps12VcsFault++;
+            }
+        }
+        else
+        {
+            ps12VcsFault = 0;
+        }
+        // IBM MFR_SPECIFIC[7] is 12V Current-Share fault.
+        if (statusMFR & 0x80)
+        {
+            if (psCS12VFault < DEGLITCH_LIMIT)
+            {
+                psCS12VFault++;
+            }
+        }
+        else
+        {
+            psCS12VFault = 0;
+        }
+    }
+}
+
+void PowerSupply::analyzeMFRFault()
+{
+    if (statusWord & phosphor::pmbus::status_word::MFR_SPECIFIC_FAULT)
+    {
+        if (mfrFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error("{SHORT_NAME} MFR fault: "
+                           "STATUS_WORD = {STATUS_WORD} "
+                           "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}",
+                           "SHORT_NAME", shortName, "STATUS_WORD",
+                           lg2::hex | lg2::field16, statusWord,
+                           "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+                           statusMFR);
+            }
+            mfrFault++;
+        }
+
+        determineMFRFault();
+    }
+    else
+    {
+        mfrFault = 0;
+    }
+}
+
+void PowerSupply::analyzeVinUVFault()
+{
+    if (statusWord & phosphor::pmbus::status_word::VIN_UV_FAULT)
+    {
+        if (vinUVFault < DEGLITCH_LIMIT)
+        {
+            if (statusWord != statusWordOld)
+            {
+                lg2::error(
+                    "{SHORT_NAME} VIN_UV fault: STATUS_WORD = {STATUS_WORD}, "
+                    "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+                    "STATUS_INPUT = {STATUS_INPUT}",
+                    "SHORT_NAME", shortName, "STATUS_WORD",
+                    lg2::hex | lg2::field16, statusWord, "STATUS_MFR_SPECIFIC",
+                    lg2::hex | lg2::field8, statusMFR, "STATUS_INPUT",
+                    lg2::hex | lg2::field8, statusInput);
+            }
+            vinUVFault++;
+        }
+        // Remember that this PSU has seen an AC fault
+        acFault = AC_FAULT_LIMIT;
+    }
+    else
+    {
+        if (vinUVFault != 0)
+        {
+            lg2::info(
+                "{SHORT_NAME} VIN_UV fault cleared: STATUS_WORD = {STATUS_WORD}, "
+                "STATUS_MFR_SPECIFIC = {STATUS_MFR_SPECIFIC}, "
+                "STATUS_INPUT = {STATUS_INPUT}",
+                "SHORT_NAME", shortName, "STATUS_WORD", lg2::hex | lg2::field16,
+                statusWord, "STATUS_MFR_SPECIFIC", lg2::hex | lg2::field8,
+                statusMFR, "STATUS_INPUT", lg2::hex | lg2::field8, statusInput);
+            vinUVFault = 0;
+        }
+        // No AC fail, decrement counter
+        if (acFault != 0)
+        {
+            --acFault;
+        }
+    }
+}
+
+void PowerSupply::analyze()
+{
+    using namespace phosphor::pmbus;
+
+    if (presenceGPIO)
+    {
+        updatePresenceGPIO();
+    }
+
+    if (present)
+    {
+        try
+        {
+            statusWordOld = statusWord;
+            statusWord = pmbusIntf->read(STATUS_WORD, Type::Debug,
+                                         (readFail < LOG_LIMIT));
+            // Read worked, reset the fail count.
+            readFail = 0;
+
+            if (statusWord)
+            {
+                statusInput = pmbusIntf->read(STATUS_INPUT, Type::Debug);
+                if (bindPath.string().find(IBMCFFPS_DD_NAME) !=
+                    std::string::npos)
+                {
+                    statusMFR = pmbusIntf->read(STATUS_MFR, Type::Debug);
+                }
+                statusCML = pmbusIntf->read(STATUS_CML, Type::Debug);
+                auto status0Vout = pmbusIntf->insertPageNum(STATUS_VOUT, 0);
+                statusVout = pmbusIntf->read(status0Vout, Type::Debug);
+                statusIout = pmbusIntf->read(STATUS_IOUT, Type::Debug);
+                statusFans12 = pmbusIntf->read(STATUS_FANS_1_2, Type::Debug);
+                statusTemperature =
+                    pmbusIntf->read(STATUS_TEMPERATURE, Type::Debug);
+
+                analyzeCMLFault();
+
+                analyzeInputFault();
+
+                analyzeVoutOVFault();
+
+                analyzeIoutOCFault();
+
+                analyzeVoutUVFault();
+
+                analyzeFanFault();
+
+                analyzeTemperatureFault();
+
+                analyzePgoodFault();
+
+                analyzeMFRFault();
+
+                analyzeVinUVFault();
+            }
+            else
+            {
+                if (statusWord != statusWordOld)
+                {
+                    lg2::info("{SHORT_NAME} STATUS_WORD = {STATUS_WORD}",
+                              "SHORT_NAME", shortName, "STATUS_WORD",
+                              lg2::hex | lg2::field16, statusWord);
+                }
+
+                // if INPUT/VIN_UV fault was on, it cleared, trace it.
+                if (inputFault)
+                {
+                    lg2::info(
+                        "{SHORT_NAME} INPUT fault cleared: STATUS_WORD = {STATUS_WORD}",
+                        "SHORT_NAME", shortName, "STATUS_WORD",
+                        lg2::hex | lg2::field16, statusWord);
+                }
+
+                if (vinUVFault)
+                {
+                    lg2::info(
+                        "{SHORT_NAME} VIN_UV cleared: STATUS_WORD = {STATUS_WORD}",
+                        "SHORT_NAME", shortName, "STATUS_WORD",
+                        lg2::hex | lg2::field16, statusWord);
+                }
+
+                if (pgoodFault > 0)
+                {
+                    lg2::info("{SHORT_NAME} pgoodFault cleared", "SHORT_NAME",
+                              shortName);
+                }
+
+                clearFaultFlags();
+                // No AC fail, decrement counter
+                if (acFault != 0)
+                {
+                    --acFault;
+                }
+            }
+
+            // Save off old inputVoltage value.
+            // Get latest inputVoltage.
+            // If voltage went from below minimum, and now is not, clear faults.
+            // Note: getInputVoltage() has its own try/catch.
+            int inputVoltageOld = inputVoltage;
+            double actualInputVoltageOld = actualInputVoltage;
+            getInputVoltage(actualInputVoltage, inputVoltage);
+            if ((inputVoltageOld == in_input::VIN_VOLTAGE_0) &&
+                (inputVoltage != in_input::VIN_VOLTAGE_0))
+            {
+                lg2::info(
+                    "{SHORT_NAME} READ_VIN back in range: actualInputVoltageOld = {ACTUAL_INPUT_VOLTAGE_OLD} "
+                    "actualInputVoltage = {ACTUAL_INPUT_VOLTAGE}",
+                    "SHORT_NAME", shortName, "ACTUAL_INPUT_VOLTAGE_OLD",
+                    actualInputVoltageOld, "ACTUAL_INPUT_VOLTAGE",
+                    actualInputVoltage);
+                clearVinUVFault();
+            }
+            else if (vinUVFault && (inputVoltage != in_input::VIN_VOLTAGE_0))
+            {
+                lg2::info(
+                    "{SHORT_NAME} CLEAR_FAULTS: vinUVFault {VIN_UV_FAULT} actualInputVoltage {ACTUAL_INPUT_VOLTAGE}",
+                    "SHORT_NAME", shortName, "VIN_UV_FAULT", vinUVFault,
+                    "ACTUAL_INPUT_VOLTAGE", actualInputVoltage);
+                // Do we have a VIN_UV fault latched that can now be cleared
+                // due to voltage back in range? Attempt to clear the
+                // fault(s), re-check faults on next call.
+                clearVinUVFault();
+            }
+            else if (std::abs(actualInputVoltageOld - actualInputVoltage) >
+                     10.0)
+            {
+                lg2::info(
+                    "{SHORT_NAME} actualInputVoltageOld = {ACTUAL_INPUT_VOLTAGE_OLD} actualInputVoltage = {ACTUAL_INPUT_VOLTAGE}",
+                    "SHORT_NAME", shortName, "ACTUAL_INPUT_VOLTAGE_OLD",
+                    actualInputVoltageOld, "ACTUAL_INPUT_VOLTAGE",
+                    actualInputVoltage);
+            }
+
+            monitorSensors();
+
+            checkAvailability();
+        }
+        catch (const ReadFailure& e)
+        {
+            if (readFail < SIZE_MAX)
+            {
+                readFail++;
+            }
+            if (readFail == LOG_LIMIT)
+            {
+                phosphor::logging::commit<ReadFailure>();
+            }
+        }
+    }
+}
+
+void PowerSupply::onOffConfig(uint8_t data)
+{
+    using namespace phosphor::pmbus;
+
+    if (present && driverName != ACBEL_FSG032_DD_NAME)
+    {
+        lg2::info("ON_OFF_CONFIG write: DATA={DATA}", "DATA",
+                  lg2::hex | lg2::field8, data);
+        try
+        {
+            std::vector<uint8_t> configData{data};
+            pmbusIntf->writeBinary(ON_OFF_CONFIG, configData,
+                                   Type::HwmonDeviceDebug);
+        }
+        catch (...)
+        {
+            // The underlying code in writeBinary will log a message to the
+            // journal if the write fails. If the ON_OFF_CONFIG is not setup
+            // as desired, later fault detection and analysis code should
+            // catch any of the fall out. We should not need to terminate
+            // the application if this write fails.
+        }
+    }
+}
+
+void PowerSupply::clearVinUVFault()
+{
+    // Read in1_lcrit_alarm to clear bits 3 and 4 of STATUS_INPUT.
+    // The fault bits in STAUTS_INPUT roll-up to STATUS_WORD. Clearing those
+    // bits in STATUS_INPUT should result in the corresponding STATUS_WORD bits
+    // also clearing.
+    //
+    // Do not care about return value. Should be 1 if active, 0 if not.
+    if (driverName != ACBEL_FSG032_DD_NAME)
+    {
+        static_cast<void>(
+            pmbusIntf->read("in1_lcrit_alarm", phosphor::pmbus::Type::Hwmon));
+    }
+    else
+    {
+        static_cast<void>(
+            pmbusIntf->read("curr1_crit_alarm", phosphor::pmbus::Type::Hwmon));
+    }
+    vinUVFault = 0;
+}
+
+void PowerSupply::clearFaults()
+{
+    lg2::debug("clearFaults() inventoryPath: {INVENTORY_PATH}",
+               "INVENTORY_PATH", inventoryPath);
+    faultLogged = false;
+    // The PMBus device driver does not allow for writing CLEAR_FAULTS
+    // directly. However, the pmbus hwmon device driver code will send a
+    // CLEAR_FAULTS after reading from any of the hwmon "files" in sysfs, so
+    // reading in1_input should result in clearing the fault bits in
+    // STATUS_BYTE/STATUS_WORD.
+    // I do not care what the return value is.
+    if (present)
+    {
+        clearFaultFlags();
+        checkAvailability();
+        readFail = 0;
+
+        try
+        {
+            clearVinUVFault();
+            static_cast<void>(
+                pmbusIntf->read("in1_input", phosphor::pmbus::Type::Hwmon));
+        }
+        catch (const ReadFailure& e)
+        {
+            // Since I do not care what the return value is, I really do not
+            // care much if it gets a ReadFailure either. However, this
+            // should not prevent the application from continuing to run, so
+            // catching the read failure.
+        }
+    }
+}
+
+void PowerSupply::inventoryChanged(sdbusplus::message_t& msg)
+{
+    std::string msgSensor;
+    std::map<std::string, std::variant<uint32_t, bool>> msgData;
+    msg.read(msgSensor, msgData);
+
+    // Check if it was the Present property that changed.
+    auto valPropMap = msgData.find(PRESENT_PROP);
+    if (valPropMap != msgData.end())
+    {
+        if (std::get<bool>(valPropMap->second))
+        {
+            present = true;
+            // TODO: Immediately trying to read or write the "files" causes
+            // read or write failures.
+            using namespace std::chrono_literals;
+            std::this_thread::sleep_for(20ms);
+            pmbusIntf->findHwmonDir();
+            onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
+            clearFaults();
+            updateInventory();
+        }
+        else
+        {
+            present = false;
+
+            // Clear out the now outdated inventory properties
+            updateInventory();
+        }
+        checkAvailability();
+    }
+}
+
+void PowerSupply::inventoryAdded(sdbusplus::message_t& msg)
+{
+    sdbusplus::message::object_path path;
+    msg.read(path);
+    // Make sure the signal is for the PSU inventory path
+    if (path == inventoryPath)
+    {
+        std::map<std::string, std::map<std::string, std::variant<bool>>>
+            interfaces;
+        // Get map of interfaces and their properties
+        msg.read(interfaces);
+
+        auto properties = interfaces.find(INVENTORY_IFACE);
+        if (properties != interfaces.end())
+        {
+            auto property = properties->second.find(PRESENT_PROP);
+            if (property != properties->second.end())
+            {
+                present = std::get<bool>(property->second);
+
+                lg2::info("Power Supply {INVENTORY_PATH} Present {PRESENT}",
+                          "INVENTORY_PATH", inventoryPath, "PRESENT", present);
+
+                updateInventory();
+                checkAvailability();
+            }
+        }
+    }
+}
+
+auto PowerSupply::readVPDValue(const std::string& vpdName,
+                               const phosphor::pmbus::Type& type,
+                               const std::size_t& vpdSize)
+{
+    std::string vpdValue;
+    const std::regex illegalVPDRegex =
+        std::regex("[^[:alnum:]]", std::regex::basic);
+
+    try
+    {
+        vpdValue = pmbusIntf->readString(vpdName, type);
+    }
+    catch (const ReadFailure& e)
+    {
+        // Ignore the read failure, let pmbus code indicate failure,
+        // path...
+        // TODO - ibm918
+        // https://github.com/openbmc/docs/blob/master/designs/vpd-collection.md
+        // The BMC must log errors if any of the VPD cannot be properly
+        // parsed or fails ECC checks.
+    }
+
+    if (vpdValue.size() != vpdSize)
+    {
+        lg2::info("{SHORT_NAME} {VPD_NAME} resize needed. size: {SIZE}",
+                  "SHORT_NAME", shortName, "VPD_NAME", vpdName, "SIZE",
+                  vpdValue.size());
+        vpdValue.resize(vpdSize, ' ');
+    }
+
+    // Replace any illegal values with space(s).
+    std::regex_replace(vpdValue.begin(), vpdValue.begin(), vpdValue.end(),
+                       illegalVPDRegex, " ");
+
+    return vpdValue;
+}
+
+void PowerSupply::updateInventory()
+{
+    using namespace phosphor::pmbus;
+
+#if IBM_VPD
+    std::string pn;
+    std::string fn;
+    std::string header;
+    std::string sn;
+    // The IBM power supply splits the full serial number into two parts.
+    // Each part is 6 bytes long, which should match up with SN_KW_SIZE.
+    const auto HEADER_SIZE = 6;
+    const auto SERIAL_SIZE = 6;
+    // The IBM PSU firmware version size is a bit complicated. It was originally
+    // 1-byte, per command. It was later expanded to 2-bytes per command, then
+    // up to 8-bytes per command. The device driver only reads up to 2 bytes per
+    // command, but combines all three of the 2-byte reads, or all 4 of the
+    // 1-byte reads into one string. So, the maximum size expected is 6 bytes.
+    // However, it is formatted by the driver as a hex string with two ASCII
+    // characters per byte.  So the maximum ASCII string size is 12.
+    const auto IBMCFFPS_FW_VERSION_SIZE = 12;
+    const auto ACBEL_FSG032_FW_VERSION_SIZE = 6;
+
+    using PropertyMap =
+        std::map<std::string,
+                 std::variant<std::string, std::vector<uint8_t>, bool>>;
+    PropertyMap assetProps;
+    PropertyMap operProps;
+    PropertyMap versionProps;
+    PropertyMap ipzvpdDINFProps;
+    PropertyMap ipzvpdVINIProps;
+    using InterfaceMap = std::map<std::string, PropertyMap>;
+    InterfaceMap interfaces;
+    using ObjectMap = std::map<sdbusplus::message::object_path, InterfaceMap>;
+    ObjectMap object;
+#endif
+    lg2::debug("updateInventory() inventoryPath: {INVENTORY_PATH}",
+               "INVENTORY_PATH", inventoryPath);
+
+    if (present)
+    {
+        // TODO: non-IBM inventory updates?
+
+#if IBM_VPD
+        if (driverName == ACBEL_FSG032_DD_NAME)
+        {
+            getPsuVpdFromDbus("CC", modelName);
+            getPsuVpdFromDbus("PN", pn);
+            getPsuVpdFromDbus("FN", fn);
+            getPsuVpdFromDbus("SN", sn);
+            assetProps.emplace(SN_PROP, sn);
+            fwVersion = readVPDValue(FW_VERSION, Type::Debug,
+                                     ACBEL_FSG032_FW_VERSION_SIZE);
+            versionProps.emplace(VERSION_PROP, fwVersion);
+        }
+        else
+        {
+            modelName = readVPDValue(CCIN, Type::HwmonDeviceDebug, CC_KW_SIZE);
+            pn = readVPDValue(PART_NUMBER, Type::Debug, PN_KW_SIZE);
+            fn = readVPDValue(FRU_NUMBER, Type::Debug, FN_KW_SIZE);
+
+            header = readVPDValue(SERIAL_HEADER, Type::Debug, HEADER_SIZE);
+            sn = readVPDValue(SERIAL_NUMBER, Type::Debug, SERIAL_SIZE);
+            assetProps.emplace(SN_PROP, header + sn);
+            fwVersion = readVPDValue(FW_VERSION, Type::HwmonDeviceDebug,
+                                     IBMCFFPS_FW_VERSION_SIZE);
+            versionProps.emplace(VERSION_PROP, fwVersion);
+        }
+
+        assetProps.emplace(MODEL_PROP, modelName);
+        assetProps.emplace(PN_PROP, pn);
+        assetProps.emplace(SPARE_PN_PROP, fn);
+
+        ipzvpdVINIProps.emplace(
+            "CC", std::vector<uint8_t>(modelName.begin(), modelName.end()));
+        ipzvpdVINIProps.emplace("PN",
+                                std::vector<uint8_t>(pn.begin(), pn.end()));
+        ipzvpdVINIProps.emplace("FN",
+                                std::vector<uint8_t>(fn.begin(), fn.end()));
+        std::string header_sn = header + sn;
+        ipzvpdVINIProps.emplace(
+            "SN", std::vector<uint8_t>(header_sn.begin(), header_sn.end()));
+        std::string description = "IBM PS";
+        ipzvpdVINIProps.emplace(
+            "DR", std::vector<uint8_t>(description.begin(), description.end()));
+
+        // Populate the VINI Resource Type (RT) keyword
+        ipzvpdVINIProps.emplace("RT", std::vector<uint8_t>{'V', 'I', 'N', 'I'});
+
+        // Update the Resource Identifier (RI) keyword
+        // 2 byte FRC: 0x0003
+        // 2 byte RID: 0x1000, 0x1001...
+        std::uint8_t num = std::stoul(
+            inventoryPath.substr(inventoryPath.size() - 1, 1), nullptr, 0);
+        std::vector<uint8_t> ri{0x00, 0x03, 0x10, num};
+        ipzvpdDINFProps.emplace("RI", ri);
+
+        // Fill in the FRU Label (FL) keyword.
+        std::string fl = "E";
+        fl.push_back(inventoryPath.back());
+        fl.resize(FL_KW_SIZE, ' ');
+        ipzvpdDINFProps.emplace("FL",
+                                std::vector<uint8_t>(fl.begin(), fl.end()));
+
+        // Populate the DINF Resource Type (RT) keyword
+        ipzvpdDINFProps.emplace("RT", std::vector<uint8_t>{'D', 'I', 'N', 'F'});
+
+        interfaces.emplace(ASSET_IFACE, std::move(assetProps));
+        interfaces.emplace(VERSION_IFACE, std::move(versionProps));
+        interfaces.emplace(DINF_IFACE, std::move(ipzvpdDINFProps));
+        interfaces.emplace(VINI_IFACE, std::move(ipzvpdVINIProps));
+
+        // Update the Functional
+        operProps.emplace(FUNCTIONAL_PROP, present);
+        interfaces.emplace(OPERATIONAL_STATE_IFACE, std::move(operProps));
+
+        auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+        object.emplace(path, std::move(interfaces));
+
+        try
+        {
+            auto service =
+                util::getService(INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
+
+            if (service.empty())
+            {
+                lg2::error("Unable to get inventory manager service");
+                return;
+            }
+
+            auto method =
+                bus.new_method_call(service.c_str(), INVENTORY_OBJ_PATH,
+                                    INVENTORY_MGR_IFACE, "Notify");
+
+            method.append(std::move(object));
+
+            auto reply = bus.call(method);
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error(
+                "Exception in updateInventory(): {ERROR}, PATH={INVENTORY_PATH}",
+                "ERROR", e, "INVENTORY_PATH", inventoryPath);
+        }
+#endif
+    }
+}
+
+auto PowerSupply::getMaxPowerOut() const
+{
+    using namespace phosphor::pmbus;
+
+    auto maxPowerOut = 0;
+
+    if (present)
+    {
+        try
+        {
+            // Read max_power_out, should be direct format
+            auto maxPowerOutStr =
+                pmbusIntf->readString(MFR_POUT_MAX, Type::HwmonDeviceDebug);
+            lg2::info("{SHORT_NAME} MFR_POUT_MAX read {MAX_POWER_OUT_STR}",
+                      "SHORT_NAME", shortName, "MAX_POWER_OUT_STR",
+                      maxPowerOutStr);
+            maxPowerOut = std::stod(maxPowerOutStr);
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("{SHORT_NAME} MFR_POUT_MAX read error: {ERROR}",
+                       "SHORT_NAME", shortName, "ERROR", e);
+        }
+    }
+
+    return maxPowerOut;
+}
+
+void PowerSupply::setupSensors()
+{
+    setupInputPowerPeakSensor();
+}
+
+void PowerSupply::setupInputPowerPeakSensor()
+{
+    if (peakInputPowerSensor || !present ||
+        (bindPath.string().find(IBMCFFPS_DD_NAME) == std::string::npos))
+    {
+        return;
+    }
+
+    // This PSU has problems with the input_history command
+    if (getMaxPowerOut() == phosphor::pmbus::IBM_CFFPS_1400W)
+    {
+        return;
+    }
+
+    auto sensorPath = std::format(
+        "/xyz/openbmc_project/sensors/power/{}_ps{}_input_power_peak",
+        chassisName, shortName.back());
+    peakInputPowerSensor = std::make_unique<PowerSensorObject>(
+        bus, sensorPath.c_str(), PowerSensorObject::action::defer_emit);
+
+    // The others can remain at the defaults.
+    peakInputPowerSensor->functional(true, true);
+    peakInputPowerSensor->available(true, true);
+    peakInputPowerSensor->value(0, true);
+    peakInputPowerSensor->unit(
+        sdbusplus::xyz::openbmc_project::Sensor::server::Value::Unit::Watts,
+        true);
+
+    auto associations = getSensorAssociations();
+    peakInputPowerSensor->associations(associations, true);
+
+    peakInputPowerSensor->emit_object_added();
+}
+
+void PowerSupply::setSensorsNotAvailable()
+{
+    if (peakInputPowerSensor)
+    {
+        peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+        peakInputPowerSensor->available(false);
+    }
+}
+
+void PowerSupply::monitorSensors()
+{
+    monitorPeakInputPowerSensor();
+}
+
+void PowerSupply::monitorPeakInputPowerSensor()
+{
+    if (!peakInputPowerSensor)
+    {
+        return;
+    }
+
+    constexpr size_t recordSize = 5;
+    std::vector<uint8_t> data;
+
+    // Get the peak input power with input history command.
+    // New data only shows up every 30s, but just try to read it every 1s
+    // anyway so we always have the most up to date value.
+    try
+    {
+        data = pmbusIntf->readBinary(INPUT_HISTORY,
+                                     pmbus::Type::HwmonDeviceDebug, recordSize);
+    }
+    catch (const ReadFailure& e)
+    {
+        peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+        peakInputPowerSensor->functional(false);
+        throw;
+    }
+
+    if (data.size() != recordSize)
+    {
+        lg2::debug(
+            "Input history command returned {DATA_SIZE} bytes instead of 5",
+            "DATA_SIZE", data.size());
+        peakInputPowerSensor->value(std::numeric_limits<double>::quiet_NaN());
+        peakInputPowerSensor->functional(false);
+        return;
+    }
+
+    // The format is SSAAAAPPPP:
+    //   SS = packet sequence number
+    //   AAAA = average power (linear format, little endian)
+    //   PPPP = peak power (linear format, little endian)
+    auto peak = static_cast<uint16_t>(data[4]) << 8 | data[3];
+    auto peakPower = linearToInteger(peak);
+
+    peakInputPowerSensor->value(peakPower);
+    peakInputPowerSensor->functional(true);
+    peakInputPowerSensor->available(true);
+}
+
+void PowerSupply::getInputVoltage(double& actualInputVoltage,
+                                  int& inputVoltage) const
+{
+    using namespace phosphor::pmbus;
+
+    actualInputVoltage = in_input::VIN_VOLTAGE_0;
+    inputVoltage = in_input::VIN_VOLTAGE_0;
+
+    if (present)
+    {
+        try
+        {
+            // Read input voltage in millivolts
+            auto inputVoltageStr = pmbusIntf->readString(READ_VIN, Type::Hwmon);
+
+            // Convert to volts
+            actualInputVoltage = std::stod(inputVoltageStr) / 1000;
+
+            // Calculate the voltage based on voltage thresholds
+            if (actualInputVoltage < in_input::VIN_VOLTAGE_MIN)
+            {
+                inputVoltage = in_input::VIN_VOLTAGE_0;
+            }
+            else if (actualInputVoltage < in_input::VIN_VOLTAGE_110_THRESHOLD)
+            {
+                inputVoltage = in_input::VIN_VOLTAGE_110;
+            }
+            else
+            {
+                inputVoltage = in_input::VIN_VOLTAGE_220;
+            }
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("{SHORT_NAME} READ_VIN read error: {ERROR}",
+                       "SHORT_NAME", shortName, "ERROR", e);
+        }
+    }
+}
+
+void PowerSupply::checkAvailability()
+{
+    bool origAvailability = available;
+    bool faulted = isPowerOn() && (hasPSKillFault() || hasIoutOCFault());
+    available = present && !hasInputFault() && !hasVINUVFault() && !faulted;
+
+    if (origAvailability != available)
+    {
+        auto invpath = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+        phosphor::power::psu::setAvailable(bus, invpath, available);
+
+        // Check if the health rollup needs to change based on the
+        // new availability value.
+        phosphor::power::psu::handleChassisHealthRollup(bus, inventoryPath,
+                                                        !available);
+    }
+}
+
+void PowerSupply::setInputVoltageRating()
+{
+    if (!present)
+    {
+        if (inputVoltageRatingIface)
+        {
+            inputVoltageRatingIface->value(0);
+            inputVoltageRatingIface.reset();
+        }
+        return;
+    }
+
+    double inputVoltageValue{};
+    int inputVoltageRating{};
+    getInputVoltage(inputVoltageValue, inputVoltageRating);
+
+    if (!inputVoltageRatingIface)
+    {
+        auto path = std::format(
+            "/xyz/openbmc_project/sensors/voltage/ps{}_input_voltage_rating",
+            shortName.back());
+
+        inputVoltageRatingIface = std::make_unique<SensorObject>(
+            bus, path.c_str(), SensorObject::action::defer_emit);
+
+        // Leave other properties at their defaults
+        inputVoltageRatingIface->unit(SensorInterface::Unit::Volts, true);
+        inputVoltageRatingIface->value(static_cast<double>(inputVoltageRating),
+                                       true);
+
+        inputVoltageRatingIface->emit_object_added();
+    }
+    else
+    {
+        inputVoltageRatingIface->value(static_cast<double>(inputVoltageRating));
+    }
+}
+
+void PowerSupply::getPsuVpdFromDbus(const std::string& keyword,
+                                    std::string& vpdStr)
+{
+    try
+    {
+        std::vector<uint8_t> value;
+        vpdStr.clear();
+        util::getProperty(VINI_IFACE, keyword, inventoryPath,
+                          INVENTORY_MGR_IFACE, bus, value);
+        for (char c : value)
+        {
+            vpdStr += c;
+        }
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error("Failed getProperty error: {ERROR}", "ERROR", e);
+    }
+}
+
+double PowerSupply::linearToInteger(uint16_t data)
+{
+    // The exponent is the first 5 bits, followed by 11 bits of mantissa.
+    int8_t exponent = (data & 0xF800) >> 11;
+    int16_t mantissa = (data & 0x07FF);
+
+    // If exponent's MSB on, then it's negative.
+    // Convert from two's complement.
+    if (exponent & 0x10)
+    {
+        exponent = (~exponent) & 0x1F;
+        exponent = (exponent + 1) * -1;
+    }
+
+    // If mantissa's MSB on, then it's negative.
+    // Convert from two's complement.
+    if (mantissa & 0x400)
+    {
+        mantissa = (~mantissa) & 0x07FF;
+        mantissa = (mantissa + 1) * -1;
+    }
+
+    auto value = static_cast<double>(mantissa) * pow(2, exponent);
+    return value;
+}
+
+std::vector<AssociationTuple> PowerSupply::getSensorAssociations()
+{
+    std::vector<AssociationTuple> associations;
+
+    associations.emplace_back("inventory", "sensors", inventoryPath);
+
+    auto chassis = getChassis(bus, inventoryPath);
+    associations.emplace_back("chassis", "all_sensors", std::move(chassis));
+
+    return associations;
+}
+
+} // namespace phosphor::power::psu
diff --git a/phosphor-power-supply/new_power_supply.hpp b/phosphor-power-supply/new_power_supply.hpp
new file mode 100644
index 0000000..162a4f8
--- /dev/null
+++ b/phosphor-power-supply/new_power_supply.hpp
@@ -0,0 +1,1099 @@
+#pragma once
+
+#include "pmbus.hpp"
+#include "types.hpp"
+#include "util.hpp"
+#include "utility.hpp"
+
+#include <gpiod.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/Availability/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/OperationalStatus/server.hpp>
+
+#include <filesystem>
+#include <stdexcept>
+
+namespace phosphor::power::psu
+{
+
+#if IBM_VPD
+// PMBus device driver "file name" to read for CCIN value.
+constexpr auto CCIN = "ccin";
+constexpr auto PART_NUMBER = "mfr_revision";
+constexpr auto FRU_NUMBER = "mfr_model";
+constexpr auto SERIAL_HEADER = "mfr_location";
+constexpr auto SERIAL_NUMBER = "mfr_serial";
+constexpr auto FW_VERSION = "fw_version";
+
+// The D-Bus property name to update with the CCIN value.
+constexpr auto MODEL_PROP = "Model";
+constexpr auto PN_PROP = "PartNumber";
+constexpr auto SPARE_PN_PROP = "SparePartNumber";
+constexpr auto SN_PROP = "SerialNumber";
+constexpr auto VERSION_PROP = "Version";
+
+// ipzVPD Keyword sizes
+static constexpr auto FL_KW_SIZE = 20;
+static constexpr auto FN_KW_SIZE = 7;
+static constexpr auto PN_KW_SIZE = 7;
+// For IBM power supplies, the SN is 6-byte header + 6-byte serial.
+static constexpr auto SN_KW_SIZE = 12;
+static constexpr auto CC_KW_SIZE = 4;
+#endif
+
+constexpr auto LOG_LIMIT = 3;
+constexpr auto DEGLITCH_LIMIT = 3;
+constexpr auto PGOOD_DEGLITCH_LIMIT = 5;
+// Number of polls to remember that an AC fault occured. Should remain greater
+// than PGOOD_DEGLITCH_LIMIT.
+constexpr auto AC_FAULT_LIMIT = 6;
+
+constexpr auto IBMCFFPS_DD_NAME = "ibm-cffps";
+constexpr auto ACBEL_FSG032_DD_NAME = "acbel-fsg032";
+
+using AvailabilityInterface =
+    sdbusplus::xyz::openbmc_project::State::Decorator::server::Availability;
+using OperationalStatusInterface = sdbusplus::xyz::openbmc_project::State::
+    Decorator::server::OperationalStatus;
+using AssocDefInterface =
+    sdbusplus::xyz::openbmc_project::Association::server::Definitions;
+using SensorInterface = sdbusplus::xyz::openbmc_project::Sensor::server::Value;
+using SensorObject = sdbusplus::server::object_t<SensorInterface>;
+using PowerSensorObject =
+    sdbusplus::server::object_t<SensorInterface, OperationalStatusInterface,
+                                AvailabilityInterface, AssocDefInterface>;
+
+using AssociationTuple = std::tuple<std::string, std::string, std::string>;
+
+/**
+ * @class PowerSupply
+ * Represents a PMBus power supply device.
+ */
+class PowerSupply
+{
+  public:
+    PowerSupply() = delete;
+    PowerSupply(const PowerSupply&) = delete;
+    PowerSupply(PowerSupply&&) = delete;
+    PowerSupply& operator=(const PowerSupply&) = delete;
+    PowerSupply& operator=(PowerSupply&&) = delete;
+    ~PowerSupply() = default;
+
+    /**
+     * @param[in] invpath - String for inventory path to use
+     * @param[in] i2cbus - The bus number this power supply is on
+     * @param[in] i2caddr - The 16-bit I2C address of the power supply
+     * @param[in] driver - i2c driver name for power supply
+     * @param[in] gpioLineName - The gpio-line-name to read for presence. See
+     * https://github.com/openbmc/docs/blob/master/designs/device-tree-gpio-naming.md
+     * @param[in] callback - Get the power on status of the psu manager class
+     */
+    PowerSupply(sdbusplus::bus_t& bus, const std::string& invpath,
+                std::uint8_t i2cbus, const std::uint16_t i2caddr,
+                const std::string& driver, const std::string& gpioLineName,
+                std::function<bool()>&& callback);
+
+    /**
+     * @param[in] invpath - String for inventory path to use
+     * @param[in] i2cbus - The bus number this power supply is on
+     * @param[in] i2caddr - The 16-bit I2C address of the power supply
+     * @param[in] driver - i2c driver name for power supply
+     * @param[in] gpioLineName - The gpio-line-name to read for presence. See
+     * https://github.com/openbmc/docs/blob/master/designs/device-tree-gpio-naming.md
+     * @param[in] callback - Get the power on status of the psu manager class
+     * @param[in] chassisShortName - Chassis name
+     */
+    PowerSupply(sdbusplus::bus_t& bus, const std::string& invpath,
+                std::uint8_t i2cbus, const std::uint16_t i2caddr,
+                const std::string& driver, const std::string& gpioLineName,
+                std::function<bool()>&& callback,
+                const std::string& chassisShortName);
+
+    phosphor::pmbus::PMBusBase& getPMBus()
+    {
+        return *pmbusIntf;
+    }
+
+    GPIOInterfaceBase* getPresenceGPIO()
+    {
+        return presenceGPIO.get();
+    }
+
+    std::string getPresenceGPIOName() const
+    {
+        if (presenceGPIO != nullptr)
+        {
+            return presenceGPIO->getName();
+        }
+        else
+        {
+            return std::string();
+        }
+    }
+
+    /**
+     * Power supply specific function to analyze for faults/errors.
+     *
+     * Various PMBus status bits will be checked for fault conditions.
+     * If a certain fault bits are on, the appropriate error will be
+     * committed.
+     */
+    void analyze();
+
+    /**
+     * Write PMBus ON_OFF_CONFIG
+     *
+     * This function will be called to cause the PMBus device driver to send the
+     * ON_OFF_CONFIG command. Takes one byte of data.
+     *
+     * @param[in] data - The ON_OFF_CONFIG data byte mask.
+     */
+    void onOffConfig(uint8_t data);
+
+    /**
+     * Clears all the member variables that indicate if a fault bit was seen as
+     * on in the STATUS_WORD or STATUS_MFR_SPECIFIC response.
+     */
+    void clearFaultFlags()
+    {
+        inputFault = 0;
+        mfrFault = 0;
+        statusMFR = 0;
+        vinUVFault = 0;
+        cmlFault = 0;
+        voutOVFault = 0;
+        ioutOCFault = 0;
+        voutUVFault = 0;
+        fanFault = 0;
+        tempFault = 0;
+        pgoodFault = 0;
+        psKillFault = 0;
+        ps12VcsFault = 0;
+        psCS12VFault = 0;
+        faultLogged = false;
+    }
+
+    /**
+     * @brief Function to specifically clear VIN_UV/OFF fault(s).
+     *
+     * The PMBus HWMON device driver has various alarm "files" to read out of
+     * sysfs. Reading those files will indicate if various alarms are active or
+     * not, and then specifically clear those faults that go with that alarm.
+     *
+     * The VIN_UV fault, indicated in STATUS_INPUT, goes with in1_lcrit_alarm.
+     * When a VIN_UV fault occurs, the "Unit Off For Insufficient Input Voltage"
+     * may also be active. Reading in1_lcrit_alarm should clear both fault bits,
+     * resulting in the corresponding fault bits in STATUS_WORD also clearing.
+     *
+     * See: https://www.kernel.org/doc/html/latest/hwmon/pmbus.html
+     */
+    void clearVinUVFault();
+
+    /**
+     * Write PMBus CLEAR_FAULTS
+     *
+     * This function will be called in various situations in order to clear
+     * any fault status bits that may have been set, in order to start over
+     * with a clean state. Presence changes and power state changes will
+     * want to clear any faults logged.
+     */
+    void clearFaults();
+
+    /**
+     * @brief Adds properties to the inventory.
+     *
+     * Reads the values from the device and writes them to the
+     * associated power supply D-Bus inventory object.
+     *
+     * This needs to be done on startup, and each time the presence
+     * state changes.
+     *
+     * Properties added:
+     * - Serial Number
+     * - Part Number
+     * - CCIN (Customer Card Identification Number) - added as the Model
+     * - Firmware version
+     */
+    void updateInventory();
+
+    /**
+     * @brief Accessor function to indicate present status
+     */
+    bool isPresent() const
+    {
+        return present;
+    }
+
+    /**
+     * @brief Returns the last read value from STATUS_WORD.
+     */
+    uint64_t getStatusWord() const
+    {
+        return statusWord;
+    }
+
+    /**
+     * @brief Returns the last read value from STATUS_INPUT.
+     */
+    uint64_t getStatusInput() const
+    {
+        return statusInput;
+    }
+
+    /**
+     * @brief Returns the last read value from STATUS_MFR.
+     */
+    uint64_t getMFRFault() const
+    {
+        return statusMFR;
+    }
+
+    /**
+     * @brief Returns the last read value from STATUS_CML.
+     */
+    uint64_t getStatusCML() const
+    {
+        return statusCML;
+    }
+
+    /**
+     * @brief Returns the last read value from STATUS_VOUT.
+     */
+    uint64_t getStatusVout() const
+    {
+        return statusVout;
+    }
+
+    /**
+     * @brief Returns the last value read from STATUS_IOUT.
+     */
+    uint64_t getStatusIout() const
+    {
+        return statusIout;
+    }
+
+    /**
+     * @brief Returns the last value read from STATUS_FANS_1_2.
+     */
+    uint64_t getStatusFans12() const
+    {
+        return statusFans12;
+    }
+
+    /**
+     * @brief Returns the last value read from STATUS_TEMPERATURE.
+     */
+    uint64_t getStatusTemperature() const
+    {
+        return statusTemperature;
+    }
+
+    /**
+     * @brief Returns true if a fault was found.
+     */
+    bool isFaulted() const
+    {
+        return (hasCommFault() || (vinUVFault >= DEGLITCH_LIMIT) ||
+                (inputFault >= DEGLITCH_LIMIT) ||
+                (voutOVFault >= DEGLITCH_LIMIT) ||
+                (ioutOCFault >= DEGLITCH_LIMIT) ||
+                (voutUVFault >= DEGLITCH_LIMIT) ||
+                (fanFault >= DEGLITCH_LIMIT) || (tempFault >= DEGLITCH_LIMIT) ||
+                (pgoodFault >= PGOOD_DEGLITCH_LIMIT) ||
+                (mfrFault >= DEGLITCH_LIMIT));
+    }
+
+    /**
+     * @brief Return whether a fault has been logged for this power supply
+     */
+    bool isFaultLogged() const
+    {
+        return faultLogged;
+    }
+
+    /**
+     * @brief Called when a fault for this power supply has been logged.
+     */
+    void setFaultLogged()
+    {
+        faultLogged = true;
+    }
+
+    /**
+     * @brief Returns true if INPUT fault occurred.
+     */
+    bool hasInputFault() const
+    {
+        return (inputFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if MFRSPECIFIC occurred.
+     */
+    bool hasMFRFault() const
+    {
+        return (mfrFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if VIN_UV_FAULT occurred.
+     */
+    bool hasVINUVFault() const
+    {
+        return (vinUVFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if VOUT_OV_FAULT occurred.
+     */
+    bool hasVoutOVFault() const
+    {
+        return (voutOVFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if IOUT_OC fault occurred (bit 4 STATUS_BYTE).
+     */
+    bool hasIoutOCFault() const
+    {
+        return (ioutOCFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if VOUT_UV_FAULT occurred.
+     */
+    bool hasVoutUVFault() const
+    {
+        return (voutUVFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     *@brief Returns true if fan fault occurred.
+     */
+    bool hasFanFault() const
+    {
+        return (fanFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if TEMPERATURE fault occurred.
+     */
+    bool hasTempFault() const
+    {
+        return (tempFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if there is a PGood fault (PGOOD# inactive, or OFF
+     * bit on).
+     */
+    bool hasPgoodFault() const
+    {
+        return (pgoodFault >= PGOOD_DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Return true if there is a PS_Kill fault.
+     */
+    bool hasPSKillFault() const
+    {
+        return (psKillFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if there is a 12Vcs (standy power) fault.
+     */
+    bool hasPS12VcsFault() const
+    {
+        return (ps12VcsFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if there is a 12V current-share fault.
+     */
+    bool hasPSCS12VFault() const
+    {
+        return (psCS12VFault >= DEGLITCH_LIMIT);
+    }
+
+    /**
+     * @brief Returns true if an AC fault has occurred in the window of
+     * interest.
+     */
+    bool hasACFault() const
+    {
+        return acFault != 0;
+    }
+
+    /**
+     * @brief Returns the device path
+     *
+     * This can be used for error call outs.
+     * Example: /sys/bus/i2c/devices/3-0068
+     */
+    const std::string getDevicePath() const
+    {
+        return pmbusIntf->path();
+    }
+
+    /**
+     * @brief Returns this power supply's inventory path.
+     *
+     * This can be used for error call outs.
+     * Example:
+     * /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply1
+     */
+    const std::string& getInventoryPath() const
+    {
+        return inventoryPath;
+    }
+
+    /**
+     * @brief Returns the short name (last part of inventoryPath).
+     */
+    const std::string& getShortName() const
+    {
+        return shortName;
+    }
+
+    /**
+     * @brief Returns the firmware revision version read from the power supply
+     */
+    const std::string& getFWVersion() const
+    {
+        return fwVersion;
+    }
+
+    /**
+     * @brief Returns the model name of the power supply
+     */
+    const std::string& getModelName() const
+    {
+        return modelName;
+    }
+
+    /**
+     * @brief Returns true if the number of failed reads exceeds limit
+     */
+    bool hasCommFault() const
+    {
+        return (readFail >= LOG_LIMIT);
+    }
+
+    /**
+     * @brief Reads the pmbus input voltage and returns that actual voltage
+     *        reading and the calculated input voltage based on thresholds.
+     * @param[out] actualInputVoltage - The actual voltage reading, in Volts.
+     * @param[out] inputVoltage - A rounded up/down value of the actual input
+     *             voltage based on thresholds, in Volts.
+     */
+    void getInputVoltage(double& actualInputVoltage, int& inputVoltage) const;
+
+    /**
+     * @brief Check if the PS is considered to be available or not
+     *
+     * It is unavailable if any of:
+     * - not present
+     * - input fault active
+     * - Vin UV fault active
+     * - PS KILL fault active
+     * - Iout OC fault active
+     *
+     * Other faults will, through creating error logs with callouts, already
+     * be setting the Functional property to false.
+     *
+     * On changes, the Available property is updated in the inventory.
+     */
+    void checkAvailability();
+
+    /**
+     * @brief Returns true when INPUT_HISTORY sync is required.
+     */
+    bool isSyncHistoryRequired() const
+    {
+        return syncHistoryRequired;
+    }
+
+    /**
+     * @brief Clears the indicator that sync required for INPUT_HISTORY.
+     *
+     * Sets variable to false to indicate that the sync is no longer required.
+     * This can be used after the PSUManager has reacted to the need for the
+     * INPUT_HISTORY data to be synchronized.
+     */
+    void clearSyncHistoryRequired()
+    {
+        syncHistoryRequired = false;
+    }
+
+    /**
+     * @brief Puts the input voltage rating on D-Bus.
+     *
+     * The rating is like 0, 110, 220.
+     */
+    void setInputVoltageRating();
+
+    /**
+     * @brief Returns the peak input power value if there is one,
+     *        otherwise std::nullopt.
+     */
+    std::optional<double> getPeakInputPower() const
+    {
+        std::optional<double> value;
+        if (peakInputPowerSensor)
+        {
+            value = peakInputPowerSensor->value();
+        }
+        return value;
+    }
+
+    /**
+     * @brief Converts a Linear Format power number to an integer
+     *
+     * The PMBus spec describes a 2 byte Linear Format
+     * number that is composed of an exponent and mantissa
+     * in two's complement notation.
+     *
+     * Value = Mantissa * 2**Exponent
+     *
+     * @return double - The converted value
+     */
+    static double linearToInteger(uint16_t data);
+
+    /**
+     * @brief Retrieve device driver name
+     */
+    const std::string& getDriverName() const
+    {
+        return driverName;
+    }
+
+    /**
+     * @brief Set device driver name
+     * @param[in] newDriver - device driver name.
+     */
+    void setDriverName(const std::string& newDriver)
+    {
+        driverName = newDriver;
+    }
+
+  private:
+    /**
+     * @brief Examine STATUS_WORD for CML (communication, memory, logic fault).
+     */
+    void analyzeCMLFault();
+
+    /**
+     * @brief Examine STATUS_WORD for INPUT bit on.
+     *
+     * "An input voltage, input current, or input power fault or warning has
+     * occurred."
+     */
+    void analyzeInputFault();
+
+    /**
+     * @brief Examine STATUS_WORD for VOUT being set.
+     *
+     * If VOUT is on, "An output voltage fault or warning has occurred.", and
+     * VOUT_OV_FAULT is on, there is an output over-voltage fault.
+     */
+    void analyzeVoutOVFault();
+
+    /**
+     * @brief Examine STATUS_WORD value read for IOUT_OC_FAULT.
+     *
+     * "An output overcurrent fault has occurred." If it is on, and fault not
+     * set, trace STATUS_WORD, STATUS_MFR_SPECIFIC, and STATUS_IOUT values.
+     */
+    void analyzeIoutOCFault();
+
+    /**
+     * @brief Examines STATUS_WORD value read to see if there is a UV fault.
+     *
+     * Checks if the VOUT bit is on, indicating "An output voltage fault or
+     * warning has occurred", if it is on, but VOUT_OV_FAULT is off, it is
+     * determined to be an indication of an output under-voltage fault.
+     */
+    void analyzeVoutUVFault();
+
+    /**
+     * @brief Examine STATUS_WORD for the fan fault/warning bit.
+     *
+     * If fanFault is not on, trace that the bit now came on, include
+     * STATUS_WORD, STATUS_MFR_SPECIFIC, and STATUS_FANS_1_2 values as well, to
+     * help with understanding what may have caused it to be set.
+     */
+    void analyzeFanFault();
+
+    /**
+     * @brief Examine STATUS_WORD for temperature fault.
+     */
+    void analyzeTemperatureFault();
+
+    /**
+     * @brief Examine STATUS_WORD for pgood or unit off faults.
+     */
+    void analyzePgoodFault();
+
+    /**
+     * @brief Determine possible manufacturer-specific faults from bits in
+     * STATUS_MFR.
+     *
+     * The bits in the STATUS_MFR_SPECIFIC command response have "Manufacturer
+     * Defined" meanings. Determine which faults, if any, are present based on
+     * the power supply (device driver) type.
+     */
+    void determineMFRFault();
+
+    /**
+     * @brief Examine STATUS_WORD value read for MFRSPECIFIC bit on.
+     *
+     * "A manufacturer specific fault or warning has occurred."
+     *
+     * If it is on, call the determineMFRFault() helper function to examine the
+     * value read from STATUS_MFR_SPECIFIC.
+     */
+    void analyzeMFRFault();
+
+    /**
+     * @brief Analyzes the STATUS_WORD for a VIN_UV_FAULT indicator.
+     */
+    void analyzeVinUVFault();
+
+    /**
+     * @brief Given a full inventory path, returns the last node of the path as
+     * the "short name"
+     */
+    std::string findShortName(const std::string& invPath)
+    {
+        const auto lastSlashPos = invPath.find_last_of('/');
+
+        if ((lastSlashPos == std::string::npos) ||
+            ((lastSlashPos + 1) == invPath.size()))
+        {
+            return invPath;
+        }
+        else
+        {
+            return invPath.substr(lastSlashPos + 1);
+        }
+    }
+
+    /**
+     * @brief Binds or unbinds the power supply device driver
+     *
+     * Called when a presence change is detected to either bind the device
+     * driver for the power supply when it is installed, or unbind the device
+     * driver when the power supply is removed.
+     *
+     * Note:
+     *    Bind device when device present and i2cbus-i2caddr does not exist
+     *    UnBind device when device not present and i2cbus-i2caddr  exist
+
+     * Writes <device> to <path>/bind (or unbind)
+     *
+     * @param present - when true, will bind the device driver
+     *                  when false, will unbind the device driver
+     */
+    void bindOrUnbindDriver(bool present);
+
+    /**
+     *  @brief Updates the presence status by querying D-Bus
+     *
+     * The D-Bus inventory properties for this power supply will be read to
+     * determine if the power supply is present or not and update this
+     * object's present member variable to reflect current status.
+     **/
+    void updatePresence();
+
+    /**
+     * @brief Updates the power supply presence by reading the GPIO line.
+     */
+    void updatePresenceGPIO();
+
+    /**
+     * @brief Callback for inventory property changes
+     *
+     * Process change of Present property for power supply.
+     *
+     * This is used if we are watching the D-Bus properties instead of reading
+     * the GPIO presence line ourselves.
+     *
+     * @param[in]  msg - Data associated with Present change signal
+     **/
+    void inventoryChanged(sdbusplus::message_t& msg);
+
+    /**
+     * @brief Callback for inventory property added.
+     *
+     * Process add of the interface with the Present property for power supply.
+     *
+     * This is used if we are watching the D-Bus properties instead of reading
+     * the GPIO presence line ourselves.
+     *
+     * @param[in]  msg - Data associated with Present add signal
+     **/
+    void inventoryAdded(sdbusplus::message_t& msg);
+
+    /**
+     * @brief Reads the pmbus MFR_POUT_MAX value.
+     *
+     * "The MFR_POUT_MAX command sets or retrieves the maximum rated output
+     * power, in watts, that the unit is rated to supply."
+     *
+     * @return max_power_out value converted from string.
+     */
+    auto getMaxPowerOut() const;
+
+    /**
+     * @brief Reads a VPD value from PMBus, correct size, and contents.
+     *
+     * If the VPD data read is not the passed in size, resize and fill with
+     * spaces. If the data contains a non-alphanumeric value, replace any of
+     * those values with spaces.
+     *
+     * @param[in] vpdName - The name of the sysfs "file" to read data from.
+     * @param[in] type - The HWMON file type to read from.
+     * @param[in] vpdSize - The expacted size of the data for this VPD/property
+     *
+     * @return A string containing the VPD data read, resized if necessary
+     */
+    auto readVPDValue(const std::string& vpdName,
+                      const phosphor::pmbus::Type& type,
+                      const std::size_t& vpdSize);
+
+    /**
+     * @brief Retrieve PSU VPD keyword from D-Bus
+     *
+     * It retrieves PSU VPD keyword from D-Bus and assign the associated
+     * string to vpdStr.
+     * @param[in] keyword - The VPD search keyword
+     * @param[out] vpdStr - The VPD string associated with the keyword.
+     */
+    void getPsuVpdFromDbus(const std::string& keyword, std::string& vpdStr);
+
+    /**
+     * @brief Creates the appropriate sensor D-Bus objects.
+     */
+    void setupSensors();
+
+    /**
+     * @brief Monitors sensor values and updates D-Bus.
+     *        Called from analyze().
+     */
+    void monitorSensors();
+
+    /**
+     * @brief Creates the peak input power sensor D-Bus object
+     *        if the PS supports it.
+     */
+    void setupInputPowerPeakSensor();
+
+    /**
+     * @brief Monitors the peak input power sensor
+     */
+    void monitorPeakInputPowerSensor();
+
+    /**
+     * @brief Sets any sensor objects to Available = false on D-Bus.
+     */
+    void setSensorsNotAvailable();
+
+    /**
+     * @brief Returns the associations to create for a sensor on this
+     *        power supply.
+     */
+    std::vector<AssociationTuple> getSensorAssociations();
+
+    /**
+     * @brief systemd bus member
+     */
+    sdbusplus::bus_t& bus;
+
+    /**
+     * @brief D-Bus path to use for this power supply's inventory status.
+     **/
+    std::string inventoryPath;
+
+    /**
+     * @brief The file system path used for binding the device driver.
+     */
+    std::filesystem::path bindPath;
+
+    /**
+     * @brief Get the power on status of the psu manager class.
+     *
+     * This is a callback method used to get the power on status of the psu
+     * manager class.
+     */
+    std::function<bool()> isPowerOn;
+
+    /**
+     * @brief Set to true when INPUT_HISTORY sync is required.
+     *
+     * A power supply will need to synchronize its INPUT_HISTORY data with the
+     * other power supplies installed in the system when it goes from missing to
+     * present.
+     */
+    bool syncHistoryRequired{false};
+
+    /**
+     * @brief Store the short name to avoid string processing.
+     *
+     * The short name will be something like powersupply1, the last part of the
+     * inventoryPath.
+     */
+    std::string shortName;
+
+    /**
+     * @brief The libgpiod object for monitoring PSU presence
+     */
+    std::unique_ptr<GPIOInterfaceBase> presenceGPIO = nullptr;
+
+    /**
+     * @brief True if the power supply is present.
+     */
+    bool present = false;
+
+    /**
+     * @brief Power supply model name.
+     */
+    std::string modelName;
+
+    /**
+     * @brief D-Bus match variable used to subscribe to Present property
+     * changes.
+     **/
+    std::unique_ptr<sdbusplus::bus::match_t> presentMatch;
+
+    /**
+     * @brief D-Bus match variable used to subscribe for Present property
+     * interface added.
+     */
+    std::unique_ptr<sdbusplus::bus::match_t> presentAddedMatch;
+
+    /**
+     * @brief Pointer to the PMBus interface
+     *
+     * Used to read or write to/from PMBus power supply devices.
+     */
+    std::unique_ptr<phosphor::pmbus::PMBusBase> pmbusIntf = nullptr;
+
+    /**
+     * @brief Stored copy of the firmware version/revision string
+     */
+    std::string fwVersion;
+
+    /**
+     * @brief The string to pass in for binding the device driver.
+     */
+    std::string bindDevice;
+
+    /**
+     * @brief The result of the most recent availability check
+     *
+     * Saved on the object so changes can be detected.
+     */
+    bool available = false;
+
+    /**
+     * @brief Will be updated to the latest/lastvalue read from STATUS_WORD.
+     */
+    uint64_t statusWord = 0;
+
+    /**
+     * @brief Will be set to the last read value of STATUS_WORD.
+     */
+    uint64_t statusWordOld = 0;
+
+    /**
+     * @brief Will be updated to the latest/lastvalue read from STATUS_INPUT.
+     */
+    uint64_t statusInput = 0;
+
+    /**
+     * @brief Will be updated to the latest/lastvalue read from STATUS_MFR.
+     */
+    uint64_t statusMFR = 0;
+
+    /**
+     * @brief Will be updated to the latest/last value read from STATUS_CML.
+     */
+    uint64_t statusCML = 0;
+
+    /**
+     * @brief Will be updated to the latest/last value read from STATUS_VOUT.
+     */
+    uint64_t statusVout = 0;
+
+    /**
+     * @brief Will be updated to the latest/last value read from STATUS_IOUT.
+     */
+    uint64_t statusIout = 0;
+
+    /**
+     * @brief Will be updated to the latest/last value read from
+     * STATUS_FANS_1_2.
+     */
+    uint64_t statusFans12 = 0;
+
+    /**
+     * @brief Will be updated to the latest/last value read from
+     * STATUS_TEMPERATURE.
+     */
+    uint64_t statusTemperature = 0;
+
+    /**
+     * @brief Will be updated with latest converted value read from READ_VIN
+     */
+    int inputVoltage = phosphor::pmbus::in_input::VIN_VOLTAGE_0;
+
+    /**
+     * @brief Will be updated with the actual voltage last read from READ_VIN
+     */
+    double actualInputVoltage = 0;
+
+    /**
+     * @brief True if an error for a fault has already been logged.
+     */
+    bool faultLogged = false;
+
+    /**
+     * @brief Incremented if bit 1 of STATUS_WORD low byte is on.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t cmlFault = 0;
+
+    /**
+     * @brief Incremented if bit 5 of STATUS_WORD high byte is on.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t inputFault = 0;
+
+    /**
+     * @brief Incremented if bit 4 of STATUS_WORD high byte is on.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t mfrFault = 0;
+
+    /**
+     * @brief Incremented if bit 3 of STATUS_WORD low byte is on.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t vinUVFault = 0;
+
+    /**
+     * @brief Incremented if bit 5 of STATUS_WORD low byte is on.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t voutOVFault = 0;
+
+    /**
+     * @brief Incremented if bit 4 of STATUS_WORD low byte is on.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t ioutOCFault = 0;
+
+    /**
+     * @brief Incremented if bit 7 of STATUS_WORD high byte is on and bit 5
+     * (VOUT_OV) of low byte is off.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t voutUVFault = 0;
+
+    /**
+     * @brief Incremented if FANS fault/warn bit on in STATUS_WORD.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t fanFault = 0;
+
+    /**
+     * @brief Incremented if bit 2 of STATUS_WORD low byte is on.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t tempFault = 0;
+
+    /**
+     * @brief Incremented if bit 11 or 6 of STATUS_WORD is on. PGOOD# is
+     * inactive, or the unit is off.
+     *
+     * Considered faulted if reaches DEGLITCH_LIMIT.
+     */
+    size_t pgoodFault = 0;
+
+    /**
+     * @brief Power Supply Kill fault.
+     *
+     * Incremented based on bits in STATUS_MFR_SPECIFIC. IBM power supplies use
+     * bit 4 to indicate this fault. Considered faulted if it reaches
+     * DEGLITCH_LIMIT.
+     */
+    size_t psKillFault = 0;
+
+    /**
+     * @brief Power Supply 12Vcs fault (standby power).
+     *
+     * Incremented based on bits in STATUS_MFR_SPECIFIC. IBM power supplies use
+     * bit 6 to indicate this fault. Considered faulted if it reaches
+     * DEGLITCH_LIMIT.
+     */
+    size_t ps12VcsFault = 0;
+
+    /**
+     * @brief Power Supply Current-Share fault in 12V domain.
+     *
+     * Incremented based on bits in STATUS_MFR_SPECIFIC. IBM power supplies use
+     * bit 7 to indicate this fault. Considered faulted if it reaches
+     * DEGLITCH_LIMIT.
+     */
+    size_t psCS12VFault = 0;
+
+    /**
+     * @brief Set to AC_FAULT_LIMIT when AC fault is detected, decremented when
+     * AC fault has cleared. Effectively forms a timer since last AC failure.
+     * Zero indicates being outside the window of concern.
+     */
+    size_t acFault = 0;
+
+    /**
+     * @brief Count of the number of read failures.
+     */
+    size_t readFail = 0;
+
+    /**
+     * @brief The D-Bus object for the input voltage rating
+     *
+     * It is updated at startup and power on.  If a power supply is
+     * added or removed after that, it does not need to be updated
+     * again (though that could be done as a future improvement).
+     */
+    std::unique_ptr<SensorObject> inputVoltageRatingIface;
+
+    /**
+     * @brief The D-Bus object for the peak input power sensor.
+     */
+    std::unique_ptr<PowerSensorObject> peakInputPowerSensor;
+
+    /**
+     * @brief The device driver name
+     */
+    std::string driverName;
+
+    /**
+     * @brief The chassis unique name
+     */
+    std::string chassisName;
+};
+
+} // namespace phosphor::power::psu