pseq: Add support for UCD90160 device

Refactor power sequencer class hierarchy to add support for the UCD90160
device.

Tested:
* Verify UCD information is obtained successfully from EntityManager
* Verify JSON configuration file is found and parsed successfully
* Test where no pgood error occurs
  * During power on
    * Verify power on continues
    * Verify no error is logged
  * After chassis is powered on
    * Verify chassis remains powered on
    * Verify no error is logged
* Test where pgood error occurs
  * During power on
    * Verify power on stops and chassis is powered off
    * Verify correct error is logged
    * Verify callouts and additional data in error log are correct
    * Detected via rail
    * Detected via pin
  * After chassis is powered on
    * Verify chassis is powered off
    * Verify correct error is logged
    * Verify callouts and additional data in error log are correct
    * Detected via rail
    * Detected via pin

Signed-off-by: Jim Wright <jlwright@us.ibm.com>
Change-Id: Ib00bc1ea34b504c245a4f0cb3979a86e51507f3c
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/phosphor-power-sequencer/README.md b/phosphor-power-sequencer/README.md
index 4783cc0..0666f41 100644
--- a/phosphor-power-sequencer/README.md
+++ b/phosphor-power-sequencer/README.md
@@ -6,7 +6,7 @@
 
 The currently implemented application is named `phosphor-power-control` and
 supports GPIO based power control and power sequencer monitoring with specific
-support for the UCD90320 device available.
+support for the UCD90320 and UCD90160 devices available.
 
 ## JSON Configuration Files
 
diff --git a/phosphor-power-sequencer/config_files/README.md b/phosphor-power-sequencer/config_files/README.md
index c90e733..c2c9374 100644
--- a/phosphor-power-sequencer/config_files/README.md
+++ b/phosphor-power-sequencer/config_files/README.md
@@ -1,13 +1,14 @@
 ## Overview
 
-The `UCD90320Monitor` class determines its device configuration from a JSON
-configuration file. The configuration file describes the power sequencer rails
-and power good (pgood) pins defined in the system.
+The `UCD90xMonitor` class and its subclasses determine the device configuration
+from a JSON configuration file. The configuration file describes the power
+sequencer rails and power good (pgood) pins defined in the system.
 
 ## File Name
 
-The expected filename format is `UCD90320Monitor_<systemType>.json`, where
-`<systemType>` is a `Compatible System` name from a `Chassis` Entity Manager
+The expected filename format is `<device>Monitor_<systemType>.json`, where
+`<device>` is the specific power sequencer device type (UCD90320 or UCD90160),
+and `<systemType>` is a `Compatible System` name from a `Chassis` Entity Manager
 configuration file.
 
 Example file name:
diff --git a/phosphor-power-sequencer/src/README.md b/phosphor-power-sequencer/src/README.md
index 70213a1..91a1580 100644
--- a/phosphor-power-sequencer/src/README.md
+++ b/phosphor-power-sequencer/src/README.md
@@ -17,9 +17,10 @@
 interface.
 
 Determines the type and I2C information of the power sequencer device with the
-Entity Manager `xyz.openbmc_project.Configuration.UCD90320` interface. If a
-specific device interface is not provided or not desired, a generic base class
-implementation (see the `PowerSequencerMonitor` class below) will be used.
+Entity Manager `xyz.openbmc_project.Configuration.UCD90320` or
+`xyz.openbmc_project.Configuration.UCD90160` interface. If a specific device
+interface is not provided or not desired, a generic base class implementation
+(see the `PowerSequencerMonitor` class below) will be used.
 
 ### PowerInterface
 
@@ -39,9 +40,18 @@
 any device specific analysis and does not need Entity Manager or JSON file
 configuration.
 
+### UCD90xMonitor
+
+Defines a base class for monitoring the UCD90\* family of power sequencer
+devices and implements the common behavior. Uses the Entity Manager
+`IBMCompatibleSystem` interface to locate its JSON based configuration. The
+loaded configuration allows pgood failure analysis to determine the specific
+voltage rail or pgood pin which has failed.
+
 ### UCD90320Monitor
 
-Implements a specific UCD90320 power sequencer device monitoring class. Uses the
-Entity Manager `IBMCompatibleSystem` interface to locate its JSON based
-configuration. The loaded configuration allows pgood failure analysis to
-determine the specific voltage rail or pgood pin which has failed.
+Implements a specific UCD90320 power sequencer device monitoring class.
+
+### UCD90160Monitor
+
+Implements a specific UCD90160 power sequencer device monitoring class.
diff --git a/phosphor-power-sequencer/src/meson.build b/phosphor-power-sequencer/src/meson.build
index 3d1c8d0..de691c9 100644
--- a/phosphor-power-sequencer/src/meson.build
+++ b/phosphor-power-sequencer/src/meson.build
@@ -9,6 +9,8 @@
     'power_control.cpp',
     'power_interface.cpp',
     'power_sequencer_monitor.cpp',
+    'ucd90x_monitor.cpp',
+    'ucd90160_monitor.cpp',
     'ucd90320_monitor.cpp',
     dependencies: [
         libgpiodcxx,
diff --git a/phosphor-power-sequencer/src/power_sequencer_monitor.cpp b/phosphor-power-sequencer/src/power_sequencer_monitor.cpp
index 603dae1..3c8f999 100644
--- a/phosphor-power-sequencer/src/power_sequencer_monitor.cpp
+++ b/phosphor-power-sequencer/src/power_sequencer_monitor.cpp
@@ -22,7 +22,6 @@
 #include <phosphor-logging/log.hpp>
 
 #include <exception>
-#include <map>
 
 namespace phosphor::power::sequencer
 {
@@ -93,13 +92,12 @@
     else if (timeout)
     {
         // Default to timeout error
-        logError("xyz.openbmc_project.Power.Error.PowerOnTimeout",
-                 additionalData);
+        logError(powerOnTimeoutError, additionalData);
     }
     else
     {
         // Default to generic pgood error
-        logError("xyz.openbmc_project.Power.Error.Shutdown", additionalData);
+        logError(shutdownError, additionalData);
     }
     if (!timeout)
     {
diff --git a/phosphor-power-sequencer/src/power_sequencer_monitor.hpp b/phosphor-power-sequencer/src/power_sequencer_monitor.hpp
index ed095bb..740b0cd 100644
--- a/phosphor-power-sequencer/src/power_sequencer_monitor.hpp
+++ b/phosphor-power-sequencer/src/power_sequencer_monitor.hpp
@@ -8,6 +8,10 @@
 namespace phosphor::power::sequencer
 {
 
+constexpr auto powerOnTimeoutError =
+    "xyz.openbmc_project.Power.Error.PowerOnTimeout";
+constexpr auto shutdownError = "xyz.openbmc_project.Power.Error.Shutdown";
+
 /**
  * @class PowerSequencerMonitor
  * Define a base class for monitoring a power sequencer device.
@@ -52,7 +56,7 @@
      */
     sdbusplus::bus_t& bus;
 
-    /*
+    /**
      * Create a BMC Dump
      */
     void createBmcDump();
diff --git a/phosphor-power-sequencer/src/ucd90160_monitor.cpp b/phosphor-power-sequencer/src/ucd90160_monitor.cpp
new file mode 100644
index 0000000..f933230
--- /dev/null
+++ b/phosphor-power-sequencer/src/ucd90160_monitor.cpp
@@ -0,0 +1,27 @@
+/**
+ * Copyright © 2022 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ucd90160_monitor.hpp"
+
+namespace phosphor::power::sequencer
+{
+
+UCD90160Monitor::UCD90160Monitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
+                                 std::uint16_t i2cAddress) :
+    UCD90xMonitor(bus, i2cBus, i2cAddress, "UCD90160", 16)
+{}
+
+} // namespace phosphor::power::sequencer
diff --git a/phosphor-power-sequencer/src/ucd90160_monitor.hpp b/phosphor-power-sequencer/src/ucd90160_monitor.hpp
new file mode 100644
index 0000000..31d4567
--- /dev/null
+++ b/phosphor-power-sequencer/src/ucd90160_monitor.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "ucd90x_monitor.hpp"
+
+#include <sdbusplus/bus.hpp>
+
+#include <cstdint>
+
+namespace phosphor::power::sequencer
+{
+
+/**
+ * @class UCD90160Monitor
+ * This class implements fault analysis for the UCD90160
+ * power sequencer device.
+ */
+class UCD90160Monitor : public UCD90xMonitor
+{
+  public:
+    UCD90160Monitor() = delete;
+    UCD90160Monitor(const UCD90160Monitor&) = delete;
+    UCD90160Monitor& operator=(const UCD90160Monitor&) = delete;
+    UCD90160Monitor(UCD90160Monitor&&) = delete;
+    UCD90160Monitor& operator=(UCD90160Monitor&&) = delete;
+    virtual ~UCD90160Monitor() = default;
+
+    /**
+     * Create a device object for UCD90160 monitoring.
+     * @param bus D-Bus bus object
+     * @param i2cBus The bus number of the power sequencer device
+     * @param i2cAddress The I2C address of the power sequencer device
+     */
+    UCD90160Monitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
+                    std::uint16_t i2cAddress);
+};
+
+} // namespace phosphor::power::sequencer
diff --git a/phosphor-power-sequencer/src/ucd90320_monitor.cpp b/phosphor-power-sequencer/src/ucd90320_monitor.cpp
index c6adcc0..818977b 100644
--- a/phosphor-power-sequencer/src/ucd90320_monitor.cpp
+++ b/phosphor-power-sequencer/src/ucd90320_monitor.cpp
@@ -16,378 +16,28 @@
 
 #include "ucd90320_monitor.hpp"
 
-#include "types.hpp"
-#include "utility.hpp"
-
 #include <fmt/format.h>
 #include <fmt/ranges.h>
 
-#include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
-#include <sdbusplus/bus.hpp>
-#include <xyz/openbmc_project/Common/Device/error.hpp>
 
-#include <chrono>
-#include <fstream>
-#include <map>
 #include <span>
-#include <string>
 
 namespace phosphor::power::sequencer
 {
 
-using json = nlohmann::json;
-using namespace pmbus;
 using namespace phosphor::logging;
-using namespace phosphor::power;
-
-const std::string compatibleInterface =
-    "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
-const std::string compatibleNamesProperty = "Names";
-
-namespace device_error = sdbusplus::xyz::openbmc_project::Common::Device::Error;
 
 UCD90320Monitor::UCD90320Monitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
                                  std::uint16_t i2cAddress) :
-    PowerSequencerMonitor(bus),
-    match{bus,
-          sdbusplus::bus::match::rules::interfacesAdded() +
-              sdbusplus::bus::match::rules::sender(
-                  "xyz.openbmc_project.EntityManager"),
-          std::bind(&UCD90320Monitor::interfacesAddedHandler, this,
-                    std::placeholders::_1)},
-    pmbusInterface{
-        fmt::format("/sys/bus/i2c/devices/{}-{:04x}", i2cBus, i2cAddress)
-            .c_str(),
-        "ucd9000", 0}
+    UCD90xMonitor(bus, i2cBus, i2cAddress, "UCD90320", 32)
+{}
 
+void UCD90320Monitor::formatGpioValues(
+    const std::vector<int>& values, unsigned int numberLines,
+    std::map<std::string, std::string>& additionalData) const
 {
-    // Use the compatible system types information, if already available, to
-    // load the configuration file
-    findCompatibleSystemTypes();
-}
-
-void UCD90320Monitor::findCompatibleSystemTypes()
-{
-    try
-    {
-        auto subTree = util::getSubTree(bus, "/xyz/openbmc_project/inventory",
-                                        compatibleInterface, 0);
-
-        auto objectIt = subTree.cbegin();
-        if (objectIt != subTree.cend())
-        {
-            const auto& objPath = objectIt->first;
-
-            // Get the first service name
-            auto serviceIt = objectIt->second.cbegin();
-            if (serviceIt != objectIt->second.cend())
-            {
-                std::string service = serviceIt->first;
-                if (!service.empty())
-                {
-                    std::vector<std::string> compatibleSystemTypes;
-
-                    // Get compatible system types property value
-                    util::getProperty(compatibleInterface,
-                                      compatibleNamesProperty, objPath, service,
-                                      bus, compatibleSystemTypes);
-
-                    log<level::DEBUG>(
-                        fmt::format("Found compatible systems: {}",
-                                    compatibleSystemTypes)
-                            .c_str());
-                    // Use compatible systems information to find config file
-                    findConfigFile(compatibleSystemTypes);
-                }
-            }
-        }
-    }
-    catch (const std::exception&)
-    {
-        // Compatible system types information is not available.
-    }
-}
-
-void UCD90320Monitor::findConfigFile(
-    const std::vector<std::string>& compatibleSystemTypes)
-{
-    // Expected config file path name:
-    // /usr/share/phosphor-power-sequencer/UCD90320Monitor_<systemType>.json
-
-    // Add possible file names based on compatible system types (if any)
-    for (const std::string& systemType : compatibleSystemTypes)
-    {
-        // Check if file exists
-        std::filesystem::path pathName{
-            "/usr/share/phosphor-power-sequencer/UCD90320Monitor_" +
-            systemType + ".json"};
-        if (std::filesystem::exists(pathName))
-        {
-            log<level::INFO>(
-                fmt::format("Config file path: {}", pathName.string()).c_str());
-            parseConfigFile(pathName);
-            break;
-        }
-    }
-}
-
-void UCD90320Monitor::interfacesAddedHandler(sdbusplus::message_t& msg)
-{
-    // Only continue if message is valid and rails / pins have not already been
-    // found
-    if (!msg || !rails.empty())
-    {
-        return;
-    }
-
-    try
-    {
-        // Read the dbus message
-        sdbusplus::message::object_path objPath;
-        std::map<std::string,
-                 std::map<std::string, std::variant<std::vector<std::string>>>>
-            interfaces;
-        msg.read(objPath, interfaces);
-
-        // Find the compatible interface, if present
-        auto itIntf = interfaces.find(compatibleInterface);
-        if (itIntf != interfaces.cend())
-        {
-            // Find the Names property of the compatible interface, if present
-            auto itProp = itIntf->second.find(compatibleNamesProperty);
-            if (itProp != itIntf->second.cend())
-            {
-                // Get value of Names property
-                const auto& propValue = std::get<0>(itProp->second);
-                if (!propValue.empty())
-                {
-                    log<level::INFO>(
-                        fmt::format(
-                            "InterfacesAdded for compatible systems: {}",
-                            propValue)
-                            .c_str());
-
-                    // Use compatible systems information to find config file
-                    findConfigFile(propValue);
-                }
-            }
-        }
-    }
-    catch (const std::exception&)
-    {
-        // Error trying to read interfacesAdded message.
-    }
-}
-
-bool UCD90320Monitor::isPresent(const std::string& inventoryPath)
-{
-    // Empty path indicates no presence check is needed
-    if (inventoryPath.empty())
-    {
-        return true;
-    }
-
-    // Get presence from D-Bus interface/property
-    try
-    {
-        bool present{true};
-        util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath,
-                          INVENTORY_MGR_IFACE, bus, present);
-        log<level::INFO>(
-            fmt::format("Presence, path: {}, value: {}", inventoryPath, present)
-                .c_str());
-        return present;
-    }
-    catch (const std::exception& e)
-    {
-        log<level::INFO>(
-            fmt::format("Error getting presence property, path: {}, error: {}",
-                        inventoryPath, e.what())
-                .c_str());
-        return false;
-    }
-}
-
-void UCD90320Monitor::parseConfigFile(const std::filesystem::path& pathName)
-{
-    try
-    {
-        std::ifstream file{pathName};
-        json rootElement = json::parse(file);
-
-        // Parse rail information from config file
-        auto railsIterator = rootElement.find("rails");
-        if (railsIterator != rootElement.end())
-        {
-            for (const auto& railElement : *railsIterator)
-            {
-                auto nameIterator = railElement.find("name");
-
-                if (nameIterator != railElement.end())
-                {
-                    Rail rail;
-                    rail.name = (*nameIterator).get<std::string>();
-
-                    // Presence element is optional
-                    auto presenceIterator = railElement.find("presence");
-                    if (presenceIterator != railElement.end())
-                    {
-                        rail.presence = (*presenceIterator).get<std::string>();
-                    }
-
-                    log<level::DEBUG>(
-                        fmt::format("Adding rail, name: {}, presence: {}",
-                                    rail.name, rail.presence)
-                            .c_str());
-                    rails.emplace_back(std::move(rail));
-                }
-                else
-                {
-                    log<level::ERR>(
-                        fmt::format(
-                            "No name found within rail in configuration file: {}",
-                            pathName.string())
-                            .c_str());
-                }
-            }
-        }
-        else
-        {
-            log<level::ERR>(
-                fmt::format("No rails found in configuration file: {}",
-                            pathName.string())
-                    .c_str());
-        }
-        log<level::DEBUG>(
-            fmt::format("Found number of rails: {}", rails.size()).c_str());
-
-        // Parse pin information from config file
-        auto pinsIterator = rootElement.find("pins");
-        if (pinsIterator != rootElement.end())
-        {
-            for (const auto& pinElement : *pinsIterator)
-            {
-                auto nameIterator = pinElement.find("name");
-                auto lineIterator = pinElement.find("line");
-
-                if (nameIterator != pinElement.end() &&
-                    lineIterator != pinElement.end())
-                {
-                    Pin pin;
-                    pin.name = (*nameIterator).get<std::string>();
-                    pin.line = (*lineIterator).get<unsigned int>();
-
-                    // Presence element is optional
-                    auto presenceIterator = pinElement.find("presence");
-                    if (presenceIterator != pinElement.end())
-                    {
-                        pin.presence = (*presenceIterator).get<std::string>();
-                    }
-
-                    log<level::DEBUG>(
-                        fmt::format(
-                            "Adding pin, name: {}, line: {}, presence: {}",
-                            pin.name, pin.line, pin.presence)
-                            .c_str());
-                    pins.emplace_back(std::move(pin));
-                }
-                else
-                {
-                    log<level::ERR>(
-                        fmt::format(
-                            "No name or line found within pin in configuration file: {}",
-                            pathName.string())
-                            .c_str());
-                }
-            }
-        }
-        else
-        {
-            log<level::ERR>(
-                fmt::format("No pins found in configuration file: {}",
-                            pathName.string())
-                    .c_str());
-        }
-        log<level::DEBUG>(
-            fmt::format("Found number of pins: {}", pins.size()).c_str());
-    }
-    catch (const std::exception& e)
-    {
-        log<level::ERR>(
-            fmt::format("Error parsing configuration file, error: {}", e.what())
-                .c_str());
-    }
-}
-
-void UCD90320Monitor::onFailure(bool timeout,
-                                const std::string& powerSupplyError)
-{
-    std::string message;
-    std::map<std::string, std::string> additionalData{};
-
-    try
-    {
-        onFailureCheckRails(message, additionalData, powerSupplyError);
-        onFailureCheckPins(message, additionalData);
-    }
-    catch (const std::exception& e)
-    {
-        log<level::ERR>(
-            fmt::format("Error when collecting metadata, error: {}", e.what())
-                .c_str());
-        additionalData.emplace("ERROR", e.what());
-    }
-
-    if (message.empty())
-    {
-        // Could not isolate, but we know something failed, so issue a timeout
-        // or generic power good error
-        message = timeout ? "xyz.openbmc_project.Power.Error.PowerOnTimeout"
-                          : "xyz.openbmc_project.Power.Error.Shutdown";
-    }
-    logError(message, additionalData);
-    if (!timeout)
-    {
-        createBmcDump();
-    }
-}
-
-void UCD90320Monitor::onFailureCheckPins(
-    std::string& message, std::map<std::string, std::string>& additionalData)
-{
-    // Setup a list of all the GPIOs on the chip
-    gpiod::chip chip{"ucd90320", gpiod::chip::OPEN_BY_LABEL};
-    log<level::INFO>(fmt::format("GPIO chip name: {}", chip.name()).c_str());
-    log<level::INFO>(fmt::format("GPIO chip label: {}", chip.label()).c_str());
-    unsigned int numberLines = chip.num_lines();
-    log<level::INFO>(
-        fmt::format("GPIO chip number of lines: {}", numberLines).c_str());
-
-    // Workaround libgpiod bulk line maximum by getting values from individual
-    // lines
-    std::vector<int> values;
-    try
-    {
-        for (unsigned int offset = 0; offset < numberLines; ++offset)
-        {
-            gpiod::line line = chip.get_line(offset);
-            line.request({"phosphor-power-control",
-                          gpiod::line_request::DIRECTION_INPUT, 0});
-            values.push_back(line.get_value());
-            line.release();
-        }
-    }
-    catch (const std::exception& e)
-    {
-        log<level::ERR>(
-            fmt::format("Error reading device GPIOs, error: {}", e.what())
-                .c_str());
-        additionalData.emplace("GPIO_ERROR", e.what());
-    }
-
-    // Add GPIO values to additional data, device has 84 GPIO pins so that value
-    // is expected
+    // Device has 84 GPIO pins so that value is expected
     if (numberLines == 84 && values.size() >= 84)
     {
         log<level::INFO>(fmt::format("MAR01-24 GPIO values: {}",
@@ -426,114 +76,6 @@
         log<level::INFO>(fmt::format("GPIO values: {}", values).c_str());
         additionalData.emplace("GPIO_VALUES", fmt::format("{}", values));
     }
-
-    // Only check GPIOs if no rail fail was found
-    if (message.empty())
-    {
-        for (size_t pin = 0; pin < pins.size(); ++pin)
-        {
-            unsigned int line = pins[pin].line;
-            if (line < values.size())
-            {
-                int value = values[line];
-
-                if ((value == 0) && isPresent(pins[pin].presence))
-                {
-                    additionalData.emplace("INPUT_NUM",
-                                           fmt::format("{}", line));
-                    additionalData.emplace("INPUT_NAME", pins[pin].name);
-                    message =
-                        "xyz.openbmc_project.Power.Error.PowerSequencerPGOODFault";
-                    return;
-                }
-            }
-        }
-    }
-}
-
-void UCD90320Monitor::onFailureCheckRails(
-    std::string& message, std::map<std::string, std::string>& additionalData,
-    const std::string& powerSupplyError)
-{
-    auto statusWord = readStatusWord();
-    additionalData.emplace("STATUS_WORD", fmt::format("{:#06x}", statusWord));
-    try
-    {
-        additionalData.emplace("MFR_STATUS",
-                               fmt::format("{:#014x}", readMFRStatus()));
-    }
-    catch (const std::exception& e)
-    {
-        log<level::ERR>(
-            fmt::format("Error when collecting MFR_STATUS, error: {}", e.what())
-                .c_str());
-        additionalData.emplace("ERROR", e.what());
-    }
-
-    // The status_word register has a summary bit to tell us if each page even
-    // needs to be checked
-    if (statusWord & status_word::VOUT_FAULT)
-    {
-        constexpr size_t numberPages = 32;
-        for (size_t page = 0; page < numberPages; page++)
-        {
-            auto statusVout = pmbusInterface.insertPageNum(STATUS_VOUT, page);
-            if (pmbusInterface.exists(statusVout, Type::Debug))
-            {
-                uint8_t vout = pmbusInterface.read(statusVout, Type::Debug);
-
-                if (vout)
-                {
-                    // If any bits are on log them, though some are just
-                    // warnings so they won't cause errors
-                    log<level::INFO>(
-                        fmt::format("{}, value: {:#04x}", statusVout, vout)
-                            .c_str());
-
-                    // Log errors if any non-warning bits on
-                    if (vout & ~status_vout::WARNING_MASK)
-                    {
-                        additionalData.emplace(
-                            fmt::format("STATUS{}_VOUT", page),
-                            fmt::format("{:#04x}", vout));
-
-                        // Base the callouts on the first present vout failure
-                        // found
-                        if (message.empty() && (page < rails.size()) &&
-                            isPresent(rails[page].presence))
-                        {
-                            additionalData.emplace("RAIL_NAME",
-                                                   rails[page].name);
-
-                            // Use power supply error if set and 12v rail has
-                            // failed, else use voltage error
-                            message =
-                                ((page == 0) && !powerSupplyError.empty())
-                                    ? powerSupplyError
-                                    : "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault";
-                        }
-                    }
-                }
-            }
-        }
-    }
-    // If no vout failure found, but power supply error is set, use power supply
-    // error
-    if (message.empty())
-    {
-        message = powerSupplyError;
-    }
-}
-
-uint16_t UCD90320Monitor::readStatusWord()
-{
-    return pmbusInterface.read(STATUS_WORD, Type::Debug);
-}
-
-uint64_t UCD90320Monitor::readMFRStatus()
-{
-    const std::string mfrStatus = "mfr_status";
-    return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug);
 }
 
 } // namespace phosphor::power::sequencer
diff --git a/phosphor-power-sequencer/src/ucd90320_monitor.hpp b/phosphor-power-sequencer/src/ucd90320_monitor.hpp
index 9bcbf77..a40456d 100644
--- a/phosphor-power-sequencer/src/ucd90320_monitor.hpp
+++ b/phosphor-power-sequencer/src/ucd90320_monitor.hpp
@@ -1,37 +1,23 @@
 #pragma once
 
-#include "pmbus.hpp"
-#include "power_sequencer_monitor.hpp"
+#include "ucd90x_monitor.hpp"
 
-#include <gpiod.hpp>
 #include <sdbusplus/bus.hpp>
-#include <sdbusplus/bus/match.hpp>
 
-#include <filesystem>
+#include <cstdint>
+#include <map>
+#include <string>
 #include <vector>
 
 namespace phosphor::power::sequencer
 {
 
-struct Pin
-{
-    std::string name;
-    unsigned int line;
-    std::string presence;
-};
-
-struct Rail
-{
-    std::string name;
-    std::string presence;
-};
-
 /**
  * @class UCD90320Monitor
  * This class implements fault analysis for the UCD90320
  * power sequencer device.
  */
-class UCD90320Monitor : public PowerSequencerMonitor
+class UCD90320Monitor : public UCD90xMonitor
 {
   public:
     UCD90320Monitor() = delete;
@@ -50,103 +36,11 @@
     UCD90320Monitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
                     std::uint16_t i2cAddress);
 
-    /**
-     * Callback function to handle interfacesAdded D-Bus signals
-     * @param msg Expanded sdbusplus message data
-     */
-    void interfacesAddedHandler(sdbusplus::message_t& msg);
-
-    /** @copydoc PowerSequencerMonitor::onFailure() */
-    void onFailure(bool timeout, const std::string& powerSupplyError) override;
-
-  private:
-    /**
-     * The match to Entity Manager interfaces added.
-     */
-    sdbusplus::bus::match_t match;
-
-    /**
-     * List of pins
-     */
-    std::vector<Pin> pins;
-
-    /**
-     * The read/write interface to this hardware
-     */
-    pmbus::PMBus pmbusInterface;
-
-    /**
-     * List of rails
-     */
-    std::vector<Rail> rails;
-
-    /**
-     * Finds the list of compatible system types using D-Bus methods.
-     * This list is used to find the correct JSON configuration file for the
-     * current system.
-     */
-    void findCompatibleSystemTypes();
-
-    /**
-     * Finds the JSON configuration file.
-     * Looks for a configuration file based on the list of compatible system
-     * types.
-     * Throws an exception if an operating system error occurs while checking
-     * for the existance of a file.
-     * @param compatibleSystemTypes List of compatible system types
-     */
-    void findConfigFile(const std::vector<std::string>& compatibleSystemTypes);
-
-    /**
-     * Returns whether the hardware with the specified inventory path is
-     * present.
-     * If an error occurs while obtaining the presence value, presence is
-     * assumed to be false. An empty string path indicates no presence check is
-     * needed.
-     * @param inventoryPath D-Bus inventory path of the hardware
-     * @return true if hardware is present, false otherwise
-     */
-    bool isPresent(const std::string& inventoryPath);
-
-    /**
-     * Analyzes the device pins for errors when the device is known to be in an
-     * error state.
-     * @param message Message property of the error log entry
-     * @param additionalData AdditionalData property of the error log entry
-     */
-    void onFailureCheckPins(std::string& message,
-                            std::map<std::string, std::string>& additionalData);
-
-    /**
-     * Analyzes the device rails for errors when the device is known to be in an
-     * error state.
-     * @param message Message property of the error log entry
-     * @param additionalData AdditionalData property of the error log entry
-     * @param powerSupplyError The power supply error to log. A default
-     * std:string, i.e. empty string (""), is passed when there is no power
-     * supply error to log.
-     */
-    void onFailureCheckRails(std::string& message,
-                             std::map<std::string, std::string>& additionalData,
-                             const std::string& powerSupplyError);
-
-    /**
-     * Parse the JSON configuration file.
-     * @param pathName the path name
-     */
-    void parseConfigFile(const std::filesystem::path& pathName);
-
-    /**
-     * Reads the mfr_status register
-     * @return the register contents
-     */
-    uint64_t readMFRStatus();
-
-    /**
-     * Reads the status_word register
-     * @return the register contents
-     */
-    uint16_t readStatusWord();
+  protected:
+    /** @copydoc UCD90xMonitor::formatGpioValues() */
+    void formatGpioValues(
+        const std::vector<int>& values, unsigned int numberLines,
+        std::map<std::string, std::string>& additionalData) const override;
 };
 
 } // namespace phosphor::power::sequencer
diff --git a/phosphor-power-sequencer/src/ucd90x_monitor.cpp b/phosphor-power-sequencer/src/ucd90x_monitor.cpp
new file mode 100644
index 0000000..2dc4675
--- /dev/null
+++ b/phosphor-power-sequencer/src/ucd90x_monitor.cpp
@@ -0,0 +1,564 @@
+/**
+ * Copyright © 2022 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ucd90x_monitor.hpp"
+
+#include "types.hpp"
+#include "utility.hpp"
+
+#include <fmt/format.h>
+#include <fmt/ranges.h>
+
+#include <gpiod.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
+
+#include <algorithm>
+#include <chrono>
+#include <exception>
+#include <fstream>
+
+namespace phosphor::power::sequencer
+{
+
+using json = nlohmann::json;
+using namespace pmbus;
+using namespace phosphor::logging;
+
+const std::string compatibleInterface =
+    "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
+const std::string compatibleNamesProperty = "Names";
+
+UCD90xMonitor::UCD90xMonitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
+                             std::uint16_t i2cAddress,
+                             const std::string& deviceName,
+                             size_t numberPages) :
+    PowerSequencerMonitor(bus),
+    deviceName{deviceName},
+    match{bus,
+          sdbusplus::bus::match::rules::interfacesAdded() +
+              sdbusplus::bus::match::rules::sender(
+                  "xyz.openbmc_project.EntityManager"),
+          std::bind(&UCD90xMonitor::interfacesAddedHandler, this,
+                    std::placeholders::_1)},
+    numberPages{numberPages},
+    pmbusInterface{
+        fmt::format("/sys/bus/i2c/devices/{}-{:04x}", i2cBus, i2cAddress)
+            .c_str(),
+        "ucd9000", 0}
+{
+    log<level::DEBUG>(
+        fmt::format("Device path: {}", pmbusInterface.path().string()).c_str());
+    log<level::DEBUG>(fmt::format("Hwmon path: {}",
+                                  pmbusInterface.getPath(Type::Hwmon).string())
+                          .c_str());
+    log<level::DEBUG>(fmt::format("Debug path: {}",
+                                  pmbusInterface.getPath(Type::Debug).string())
+                          .c_str());
+    log<level::DEBUG>(
+        fmt::format("Device debug path: {}",
+                    pmbusInterface.getPath(Type::DeviceDebug).string())
+            .c_str());
+    log<level::DEBUG>(
+        fmt::format("Hwmon device debug path: {}",
+                    pmbusInterface.getPath(Type::HwmonDeviceDebug).string())
+            .c_str());
+
+    // Use the compatible system types information, if already available, to
+    // load the configuration file
+    findCompatibleSystemTypes();
+}
+
+void UCD90xMonitor::findCompatibleSystemTypes()
+{
+    try
+    {
+        auto subTree = util::getSubTree(bus, "/xyz/openbmc_project/inventory",
+                                        compatibleInterface, 0);
+
+        auto objectIt = subTree.cbegin();
+        if (objectIt != subTree.cend())
+        {
+            const auto& objPath = objectIt->first;
+
+            // Get the first service name
+            auto serviceIt = objectIt->second.cbegin();
+            if (serviceIt != objectIt->second.cend())
+            {
+                std::string service = serviceIt->first;
+                if (!service.empty())
+                {
+                    std::vector<std::string> compatibleSystemTypes;
+
+                    // Get compatible system types property value
+                    util::getProperty(compatibleInterface,
+                                      compatibleNamesProperty, objPath, service,
+                                      bus, compatibleSystemTypes);
+
+                    log<level::DEBUG>(
+                        fmt::format("Found compatible systems: {}",
+                                    compatibleSystemTypes)
+                            .c_str());
+                    // Use compatible systems information to find config file
+                    findConfigFile(compatibleSystemTypes);
+                }
+            }
+        }
+    }
+    catch (const std::exception&)
+    {
+        // Compatible system types information is not available.
+    }
+}
+
+void UCD90xMonitor::findConfigFile(
+    const std::vector<std::string>& compatibleSystemTypes)
+{
+    // Expected config file path name:
+    // /usr/share/phosphor-power-sequencer/<deviceName>Monitor_<systemType>.json
+
+    // Add possible file names based on compatible system types (if any)
+    for (const std::string& systemType : compatibleSystemTypes)
+    {
+        // Check if file exists
+        std::filesystem::path pathName{"/usr/share/phosphor-power-sequencer/" +
+                                       deviceName + "Monitor_" + systemType +
+                                       ".json"};
+        log<level::DEBUG>(
+            fmt::format("Attempting config file path: {}", pathName.string())
+                .c_str());
+        if (std::filesystem::exists(pathName))
+        {
+            log<level::INFO>(
+                fmt::format("Config file path: {}", pathName.string()).c_str());
+            parseConfigFile(pathName);
+            break;
+        }
+    }
+}
+
+void UCD90xMonitor::interfacesAddedHandler(sdbusplus::message_t& msg)
+{
+    // Only continue if message is valid and rails / pins have not already been
+    // found
+    if (!msg || !rails.empty())
+    {
+        return;
+    }
+
+    try
+    {
+        // Read the dbus message
+        sdbusplus::message::object_path objPath;
+        std::map<std::string,
+                 std::map<std::string, std::variant<std::vector<std::string>>>>
+            interfaces;
+        msg.read(objPath, interfaces);
+
+        // Find the compatible interface, if present
+        auto itIntf = interfaces.find(compatibleInterface);
+        if (itIntf != interfaces.cend())
+        {
+            // Find the Names property of the compatible interface, if present
+            auto itProp = itIntf->second.find(compatibleNamesProperty);
+            if (itProp != itIntf->second.cend())
+            {
+                // Get value of Names property
+                const auto& propValue = std::get<0>(itProp->second);
+                if (!propValue.empty())
+                {
+                    log<level::INFO>(
+                        fmt::format(
+                            "InterfacesAdded for compatible systems: {}",
+                            propValue)
+                            .c_str());
+
+                    // Use compatible systems information to find config file
+                    findConfigFile(propValue);
+                }
+            }
+        }
+    }
+    catch (const std::exception&)
+    {
+        // Error trying to read interfacesAdded message.
+    }
+}
+
+bool UCD90xMonitor::isPresent(const std::string& inventoryPath)
+{
+    // Empty path indicates no presence check is needed
+    if (inventoryPath.empty())
+    {
+        return true;
+    }
+
+    // Get presence from D-Bus interface/property
+    try
+    {
+        bool present{true};
+        util::getProperty(INVENTORY_IFACE, PRESENT_PROP, inventoryPath,
+                          INVENTORY_MGR_IFACE, bus, present);
+        log<level::INFO>(
+            fmt::format("Presence, path: {}, value: {}", inventoryPath, present)
+                .c_str());
+        return present;
+    }
+    catch (const std::exception& e)
+    {
+        log<level::INFO>(
+            fmt::format("Error getting presence property, path: {}, error: {}",
+                        inventoryPath, e.what())
+                .c_str());
+        return false;
+    }
+}
+
+void UCD90xMonitor::formatGpioValues(
+    const std::vector<int>& values, unsigned int /*numberLines*/,
+    std::map<std::string, std::string>& additionalData) const
+{
+    log<level::INFO>(fmt::format("GPIO values: {}", values).c_str());
+    additionalData.emplace("GPIO_VALUES", fmt::format("{}", values));
+}
+
+void UCD90xMonitor::onFailure(bool timeout, const std::string& powerSupplyError)
+{
+    std::string message;
+    std::map<std::string, std::string> additionalData{};
+
+    try
+    {
+        onFailureCheckRails(message, additionalData, powerSupplyError);
+        log<level::DEBUG>(
+            fmt::format("After onFailureCheckRails, message: {}", message)
+                .c_str());
+        onFailureCheckPins(message, additionalData);
+        log<level::DEBUG>(
+            fmt::format("After onFailureCheckPins, message: {}", message)
+                .c_str());
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("Error when collecting metadata, error: {}", e.what())
+                .c_str());
+        additionalData.emplace("ERROR", e.what());
+    }
+
+    if (message.empty())
+    {
+        // Could not isolate, but we know something failed, so issue a timeout
+        // or generic power good error
+        message = timeout ? powerOnTimeoutError : shutdownError;
+    }
+    logError(message, additionalData);
+    if (!timeout)
+    {
+        createBmcDump();
+    }
+}
+
+void UCD90xMonitor::onFailureCheckPins(
+    std::string& message, std::map<std::string, std::string>& additionalData)
+{
+    // Create a lower case version of device name to use as label in libgpiod
+    std::string label{deviceName};
+    std::transform(label.begin(), label.end(), label.begin(), ::tolower);
+
+    // Setup a list of all the GPIOs on the chip
+    gpiod::chip chip{label, gpiod::chip::OPEN_BY_LABEL};
+    log<level::INFO>(fmt::format("GPIO chip name: {}", chip.name()).c_str());
+    log<level::INFO>(fmt::format("GPIO chip label: {}", chip.label()).c_str());
+    unsigned int numberLines = chip.num_lines();
+    log<level::INFO>(
+        fmt::format("GPIO chip number of lines: {}", numberLines).c_str());
+
+    // Workaround libgpiod bulk line maximum by getting values from individual
+    // lines
+    std::vector<int> values;
+    try
+    {
+        for (unsigned int offset = 0; offset < numberLines; ++offset)
+        {
+            gpiod::line line = chip.get_line(offset);
+            line.request({"phosphor-power-control",
+                          gpiod::line_request::DIRECTION_INPUT, 0});
+            values.push_back(line.get_value());
+            line.release();
+        }
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("Error reading device GPIOs, error: {}", e.what())
+                .c_str());
+        additionalData.emplace("GPIO_ERROR", e.what());
+    }
+
+    formatGpioValues(values, numberLines, additionalData);
+
+    // Only check GPIOs if no rail fail was found
+    if (message.empty())
+    {
+        for (size_t pin = 0; pin < pins.size(); ++pin)
+        {
+            unsigned int line = pins[pin].line;
+            if (line < values.size())
+            {
+                int value = values[line];
+
+                if ((value == 0) && isPresent(pins[pin].presence))
+                {
+                    additionalData.emplace("INPUT_NUM",
+                                           fmt::format("{}", line));
+                    additionalData.emplace("INPUT_NAME", pins[pin].name);
+                    message =
+                        "xyz.openbmc_project.Power.Error.PowerSequencerPGOODFault";
+                    return;
+                }
+            }
+        }
+    }
+}
+
+void UCD90xMonitor::onFailureCheckRails(
+    std::string& message, std::map<std::string, std::string>& additionalData,
+    const std::string& powerSupplyError)
+{
+    auto statusWord = readStatusWord();
+    additionalData.emplace("STATUS_WORD", fmt::format("{:#06x}", statusWord));
+    try
+    {
+        additionalData.emplace("MFR_STATUS",
+                               fmt::format("{:#014x}", readMFRStatus()));
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("Error when collecting MFR_STATUS, error: {}", e.what())
+                .c_str());
+        additionalData.emplace("ERROR", e.what());
+    }
+
+    // The status_word register has a summary bit to tell us if each page even
+    // needs to be checked
+    if (statusWord & status_word::VOUT_FAULT)
+    {
+        for (size_t page = 0; page < numberPages; page++)
+        {
+            auto statusVout = pmbusInterface.insertPageNum(STATUS_VOUT, page);
+            if (pmbusInterface.exists(statusVout, Type::Debug))
+            {
+                uint8_t vout = pmbusInterface.read(statusVout, Type::Debug);
+
+                if (vout)
+                {
+                    // If any bits are on log them, though some are just
+                    // warnings so they won't cause errors
+                    log<level::INFO>(
+                        fmt::format("{}, value: {:#04x}", statusVout, vout)
+                            .c_str());
+
+                    // Log errors if any non-warning bits on
+                    if (vout & ~status_vout::WARNING_MASK)
+                    {
+                        additionalData.emplace(
+                            fmt::format("STATUS{}_VOUT", page),
+                            fmt::format("{:#04x}", vout));
+
+                        // Base the callouts on the first present vout failure
+                        // found
+                        if (message.empty() && (page < rails.size()) &&
+                            isPresent(rails[page].presence))
+                        {
+                            additionalData.emplace("RAIL_NAME",
+                                                   rails[page].name);
+
+                            // Use power supply error if set and 12v rail has
+                            // failed, else use voltage error
+                            message =
+                                ((page == 0) && !powerSupplyError.empty())
+                                    ? powerSupplyError
+                                    : "xyz.openbmc_project.Power.Error.PowerSequencerVoltageFault";
+                        }
+                    }
+                }
+            }
+        }
+    }
+    // If no vout failure found, but power supply error is set, use power supply
+    // error
+    if (message.empty())
+    {
+        message = powerSupplyError;
+    }
+}
+
+void UCD90xMonitor::parseConfigFile(const std::filesystem::path& pathName)
+{
+    try
+    {
+        log<level::DEBUG>(
+            std::string("Loading configuration file " + pathName.string())
+                .c_str());
+
+        std::ifstream file{pathName};
+        json rootElement = json::parse(file);
+        log<level::DEBUG>(fmt::format("Parsed, root element is_object: {}",
+                                      rootElement.is_object())
+                              .c_str());
+
+        // Parse rail information from config file
+        auto railsIterator = rootElement.find("rails");
+        if (railsIterator != rootElement.end())
+        {
+            for (const auto& railElement : *railsIterator)
+            {
+                log<level::DEBUG>(fmt::format("Rail element is_object: {}",
+                                              railElement.is_object())
+                                      .c_str());
+
+                auto nameIterator = railElement.find("name");
+                if (nameIterator != railElement.end())
+                {
+                    log<level::DEBUG>(fmt::format("Name element is_string: {}",
+                                                  (*nameIterator).is_string())
+                                          .c_str());
+                    Rail rail;
+                    rail.name = (*nameIterator).get<std::string>();
+
+                    // Presence element is optional
+                    auto presenceIterator = railElement.find("presence");
+                    if (presenceIterator != railElement.end())
+                    {
+                        log<level::DEBUG>(
+                            fmt::format("Presence element is_string: {}",
+                                        (*presenceIterator).is_string())
+                                .c_str());
+
+                        rail.presence = (*presenceIterator).get<std::string>();
+                    }
+
+                    log<level::DEBUG>(
+                        fmt::format("Adding rail, name: {}, presence: {}",
+                                    rail.name, rail.presence)
+                            .c_str());
+                    rails.emplace_back(std::move(rail));
+                }
+                else
+                {
+                    log<level::ERR>(
+                        fmt::format(
+                            "No name found within rail in configuration file: {}",
+                            pathName.string())
+                            .c_str());
+                }
+            }
+        }
+        else
+        {
+            log<level::ERR>(
+                fmt::format("No rails found in configuration file: {}",
+                            pathName.string())
+                    .c_str());
+        }
+        log<level::DEBUG>(
+            fmt::format("Found number of rails: {}", rails.size()).c_str());
+
+        // Parse pin information from config file
+        auto pinsIterator = rootElement.find("pins");
+        if (pinsIterator != rootElement.end())
+        {
+            for (const auto& pinElement : *pinsIterator)
+            {
+                log<level::DEBUG>(fmt::format("Pin element is_object: {}",
+                                              pinElement.is_object())
+                                      .c_str());
+                auto nameIterator = pinElement.find("name");
+                auto lineIterator = pinElement.find("line");
+                if (nameIterator != pinElement.end() &&
+                    lineIterator != pinElement.end())
+                {
+                    log<level::DEBUG>(fmt::format("Name element is_string: {}",
+                                                  (*nameIterator).is_string())
+                                          .c_str());
+                    log<level::DEBUG>(
+                        fmt::format("Line element is_number_integer: {}",
+                                    (*lineIterator).is_number_integer())
+                            .c_str());
+                    Pin pin;
+                    pin.name = (*nameIterator).get<std::string>();
+                    pin.line = (*lineIterator).get<unsigned int>();
+
+                    // Presence element is optional
+                    auto presenceIterator = pinElement.find("presence");
+                    if (presenceIterator != pinElement.end())
+                    {
+                        log<level::DEBUG>(
+                            fmt::format("Presence element is_string: {}",
+                                        (*presenceIterator).is_string())
+                                .c_str());
+                        pin.presence = (*presenceIterator).get<std::string>();
+                    }
+
+                    log<level::DEBUG>(
+                        fmt::format(
+                            "Adding pin, name: {}, line: {}, presence: {}",
+                            pin.name, pin.line, pin.presence)
+                            .c_str());
+                    pins.emplace_back(std::move(pin));
+                }
+                else
+                {
+                    log<level::ERR>(
+                        fmt::format(
+                            "No name or line found within pin in configuration file: {}",
+                            pathName.string())
+                            .c_str());
+                }
+            }
+        }
+        else
+        {
+            log<level::ERR>(
+                fmt::format("No pins found in configuration file: {}",
+                            pathName.string())
+                    .c_str());
+        }
+        log<level::DEBUG>(
+            fmt::format("Found number of pins: {}", pins.size()).c_str());
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("Error parsing configuration file, error: {}", e.what())
+                .c_str());
+    }
+}
+
+uint16_t UCD90xMonitor::readStatusWord()
+{
+    return pmbusInterface.read(STATUS_WORD, Type::Debug);
+}
+
+uint64_t UCD90xMonitor::readMFRStatus()
+{
+    const std::string mfrStatus = "mfr_status";
+    return pmbusInterface.read(mfrStatus, Type::HwmonDeviceDebug);
+}
+
+} // namespace phosphor::power::sequencer
diff --git a/phosphor-power-sequencer/src/ucd90x_monitor.hpp b/phosphor-power-sequencer/src/ucd90x_monitor.hpp
new file mode 100644
index 0000000..f52046c
--- /dev/null
+++ b/phosphor-power-sequencer/src/ucd90x_monitor.hpp
@@ -0,0 +1,178 @@
+#pragma once
+
+#include "pmbus.hpp"
+#include "power_sequencer_monitor.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <cstdint>
+#include <filesystem>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace phosphor::power::sequencer
+{
+
+struct Pin
+{
+    std::string name;
+    unsigned int line;
+    std::string presence;
+};
+
+struct Rail
+{
+    std::string name;
+    std::string presence;
+};
+
+/**
+ * @class UCD90xMonitor
+ * Define a base class for monitoring the UCD90* family of power sequencer
+ * devices.
+ */
+class UCD90xMonitor : public PowerSequencerMonitor
+{
+  public:
+    UCD90xMonitor() = delete;
+    UCD90xMonitor(const UCD90xMonitor&) = delete;
+    UCD90xMonitor& operator=(const UCD90xMonitor&) = delete;
+    UCD90xMonitor(UCD90xMonitor&&) = delete;
+    UCD90xMonitor& operator=(UCD90xMonitor&&) = delete;
+    virtual ~UCD90xMonitor() = default;
+
+    /**
+     * Create a base object for UCD90* monitoring.
+     * @param bus D-Bus bus object
+     * @param i2cBus The bus number of the power sequencer device
+     * @param i2cAddress The I2C address of the power sequencer device
+     * @param deviceName The name of the device
+     * @param numberPages The number of pages the PMBus device supports
+     */
+    UCD90xMonitor(sdbusplus::bus_t& bus, std::uint8_t i2cBus,
+                  std::uint16_t i2cAddress, const std::string& deviceName,
+                  size_t numberPages);
+
+    /**
+     * Callback function to handle interfacesAdded D-Bus signals
+     * @param msg Expanded sdbusplus message data
+     */
+    void interfacesAddedHandler(sdbusplus::message_t& msg);
+
+    /** @copydoc PowerSequencerMonitor::onFailure() */
+    void onFailure(bool timeout, const std::string& powerSupplyError) override;
+
+  protected:
+    /**
+     * Formats the GPIO values read from the device.
+     * @param values List of GPIO values
+     * @param numberLines Number of GPIO lines
+     * @param additionalData AdditionalData property of the error log entry
+     */
+    virtual void formatGpioValues(
+        const std::vector<int>& values, unsigned int numberLines,
+        std::map<std::string, std::string>& additionalData) const;
+
+  private:
+    /**
+     * Device name
+     */
+    std::string deviceName;
+
+    /**
+     * The match to Entity Manager interfaces added.
+     */
+    sdbusplus::bus::match_t match;
+
+    /**
+     * The number of pages the PMBus device supports
+     */
+    size_t numberPages;
+
+    /**
+     * List of pins
+     */
+    std::vector<Pin> pins;
+
+    /**
+     * The read/write interface to this hardware
+     */
+    pmbus::PMBus pmbusInterface;
+
+    /**
+     * List of rails
+     */
+    std::vector<Rail> rails;
+
+    /**
+     * Finds the list of compatible system types using D-Bus methods.
+     * This list is used to find the correct JSON configuration file for the
+     * current system.
+     */
+    void findCompatibleSystemTypes();
+
+    /**
+     * Finds the JSON configuration file.
+     * Looks for a configuration file based on the list of compatible system
+     * types.
+     * Throws an exception if an operating system error occurs while checking
+     * for the existance of a file.
+     * @param compatibleSystemTypes List of compatible system types
+     */
+    void findConfigFile(const std::vector<std::string>& compatibleSystemTypes);
+
+    /**
+     * Returns whether the hardware with the specified inventory path is
+     * present.
+     * If an error occurs while obtaining the presence value, presence is
+     * assumed to be false. An empty string path indicates no presence check is
+     * needed.
+     * @param inventoryPath D-Bus inventory path of the hardware
+     * @return true if hardware is present, false otherwise
+     */
+    bool isPresent(const std::string& inventoryPath);
+
+    /**
+     * Analyzes the device pins for errors when the device is known to be in an
+     * error state.
+     * @param message Message property of the error log entry
+     * @param additionalData AdditionalData property of the error log entry
+     */
+    void onFailureCheckPins(std::string& message,
+                            std::map<std::string, std::string>& additionalData);
+
+    /**
+     * Analyzes the device rails for errors when the device is known to be in an
+     * error state.
+     * @param message Message property of the error log entry
+     * @param additionalData AdditionalData property of the error log entry
+     * @param powerSupplyError The power supply error to log. A default
+     * std:string, i.e. empty string (""), is passed when there is no power
+     * supply error to log.
+     */
+    void onFailureCheckRails(std::string& message,
+                             std::map<std::string, std::string>& additionalData,
+                             const std::string& powerSupplyError);
+
+    /**
+     * Parse the JSON configuration file.
+     * @param pathName the path name
+     */
+    void parseConfigFile(const std::filesystem::path& pathName);
+
+    /**
+     * Reads the mfr_status register
+     * @return the register contents
+     */
+    uint64_t readMFRStatus();
+
+    /**
+     * Reads the status_word register
+     * @return the register contents
+     */
+    uint16_t readStatusWord();
+};
+
+} // namespace phosphor::power::sequencer