psu-ng: Add a clearVinUVFault function

Create a clearVinUVFault() function specifically for clearing the VIN_UV
and unit is off fault bits.

Use this function when see a vinUVFault with good voltage read back from
READ_VIN. This should clear the latched fault if the fault is no longer
active. If the fault is still active, the next read will show the fault
again.

Also call clearVinUVFault() from the clearFaults() function.

Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
Change-Id: Ic26029fe64e3cd3e232c615b223cafb774bf77f9
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index 7c8c646..4b43cb2 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -607,7 +607,7 @@
                         "actualInputVoltage = {}",
                         shortName, actualInputVoltageOld, actualInputVoltage)
                         .c_str());
-                clearFaults();
+                clearVinUVFault();
             }
             else if (vinUVFault && (inputVoltage != in_input::VIN_VOLTAGE_0))
             {
@@ -617,9 +617,9 @@
                         shortName, vinUVFault, actualInputVoltage)
                         .c_str());
                 // Do we have a VIN_UV fault latched that can now be cleared
-                // due to voltage back in range? Attempt to clear all
-                // faults, re-check faults on next call.
-                clearFaults();
+                // 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) > 1.0)
             {
@@ -670,6 +670,19 @@
     }
 }
 
+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.
+    static_cast<void>(
+        pmbusIntf->read("in1_lcrit_alarm", phosphor::pmbus::Type::Hwmon));
+    vinUVFault = 0;
+}
+
 void PowerSupply::clearFaults()
 {
     log<level::DEBUG>(
@@ -689,6 +702,7 @@
 
         try
         {
+            clearVinUVFault();
             static_cast<void>(
                 pmbusIntf->read("in1_input", phosphor::pmbus::Type::Hwmon));
         }
diff --git a/phosphor-power-supply/power_supply.hpp b/phosphor-power-supply/power_supply.hpp
index 3cc7fcb..6f1044c 100644
--- a/phosphor-power-supply/power_supply.hpp
+++ b/phosphor-power-supply/power_supply.hpp
@@ -126,6 +126,22 @@
     }
 
     /**
+     * @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
diff --git a/phosphor-power-supply/test/power_supply_tests.cpp b/phosphor-power-supply/test/power_supply_tests.cpp
index 63f03db..1ce91f9 100644
--- a/phosphor-power-supply/test/power_supply_tests.cpp
+++ b/phosphor-power-supply/test/power_supply_tests.cpp
@@ -103,14 +103,15 @@
     EXPECT_CALL(pmbus, writeBinary(ON_OFF_CONFIG, _, _));
     // Presence change from missing to present will trigger in1_input read
     // in an attempt to get CLEAR_FAULTS called.
-    // The voltage defaults to 0, the first call to analyze should update the
-    // voltage to the current reading, triggering another clearing of faults
-    // due to below minimum to within range voltage.
     // This READ_VIN for CLEAR_FAULTS does not check the returned value.
-    EXPECT_CALL(pmbus, read(READ_VIN, _, _))
+    EXPECT_CALL(pmbus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(1));
+    // The call for clearing faults includes clearing VIN_UV fault.
+    // The voltage defaults to 0, the first call to analyze should update the
+    // voltage to the current reading, triggering clearing VIN_UV fault(s)
+    // due to below minimum to within range voltage.
+    EXPECT_CALL(pmbus, read("in1_lcrit_alarm", _, _))
         .Times(2)
-        .WillOnce(Return(1))
-        .WillOnce(Return(2));
+        .WillRepeatedly(Return(1));
     // Missing/present call will update Presence in inventory.
     EXPECT_CALL(util, setPresence(_, _, true, _));
 }
@@ -283,7 +284,8 @@
         // Update expectations for STATUS_WORD input fault/warn
         // STATUS_INPUT fault bits ... on.
         expectations.statusWordValue = (status_word::INPUT_FAULT_WARN);
-        expectations.statusInputValue = 0x38;
+        // IIN_OC fault.
+        expectations.statusInputValue = 0x04;
 
         for (auto x = 1; x <= DEGLITCH_LIMIT; x++)
         {
@@ -312,6 +314,9 @@
     }
 
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(1));
+    EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
+        .Times(1)
+        .WillOnce(Return(1));
     psu2.clearFaults();
 
     // STATUS_WORD INPUT/UV fault.
@@ -329,7 +334,7 @@
         expectations.statusWordValue =
             (status_word::INPUT_FAULT_WARN | status_word::VIN_UV_FAULT);
         // STATUS_INPUT fault bits ... on.
-        expectations.statusInputValue = 0x38;
+        expectations.statusInputValue = 0x18;
         for (auto x = 1; x <= DEGLITCH_LIMIT; x++)
         {
             setPMBusExpectations(mockPMBus, expectations);
@@ -363,10 +368,10 @@
         EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
             .Times(1)
             .WillOnce(Return("209000"));
-        // The call for CLEAR_FAULTS command
-        EXPECT_CALL(mockPMBus, read(READ_VIN, _, _))
+        // The call to clear VIN_UV/Off fault(s)
+        EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
             .Times(1)
-            .WillOnce(Return(3));
+            .WillOnce(Return(1));
         psu2.analyze();
         // Should remain present, no longer be faulted, no input fault, no
         // VIN_UV fault. Nothing else should change.
@@ -377,6 +382,9 @@
     }
 
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(1));
+    EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
+        .Times(1)
+        .WillOnce(Return(1));
     psu2.clearFaults();
 
     // STATUS_WORD MFR fault.
@@ -419,7 +427,11 @@
     }
 
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(1));
+    EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
+        .Times(1)
+        .WillOnce(Return(1));
     psu2.clearFaults();
+
     // Temperature fault.
     {
         // First STATUS_WORD with no bits set, then with temperature fault.
@@ -459,7 +471,11 @@
     }
 
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(1));
+    EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
+        .Times(1)
+        .WillOnce(Return(1));
     psu2.clearFaults();
+
     // CML fault
     {
         // First STATUS_WORD wit no bits set, then with CML fault.
@@ -499,7 +515,11 @@
     }
 
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(1));
+    EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
+        .Times(1)
+        .WillOnce(Return(1));
     psu2.clearFaults();
+
     // VOUT_OV_FAULT fault
     {
         // First STATUS_WORD with no bits set, then with VOUT/VOUT_OV fault.
@@ -834,6 +854,10 @@
     EXPECT_CALL(mockPMBus, read(READ_VIN, _, _))
         .Times(1)
         .WillOnce(Return(207000));
+    // Clearing VIN_UV fault via in1_lcrit_alarm
+    EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
+        .Times(1)
+        .WillOnce(Return(1));
     EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.clearFaults();
     EXPECT_EQ(psu.isPresent(), true);
@@ -912,7 +936,35 @@
     EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
         .Times(1)
         .WillOnce(Return("206000"));
-    EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(0));
+    // VIN_UV cleared via in1_lcrit_alarm when voltage back in range.
+    EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
+        .Times(1)
+        .WillOnce(Return(1));
+    psu.analyze();
+    // We only cleared the VIN_UV and OFF faults.
+    EXPECT_EQ(psu.isPresent(), true);
+    EXPECT_EQ(psu.isFaulted(), true);
+    EXPECT_EQ(psu.hasInputFault(), false);
+    EXPECT_EQ(psu.hasMFRFault(), true);
+    EXPECT_EQ(psu.hasVINUVFault(), false);
+    EXPECT_EQ(psu.hasCommFault(), true);
+    EXPECT_EQ(psu.hasVoutOVFault(), true);
+    EXPECT_EQ(psu.hasIoutOCFault(), true);
+    EXPECT_EQ(psu.hasVoutUVFault(), false);
+    EXPECT_EQ(psu.hasFanFault(), true);
+    EXPECT_EQ(psu.hasTempFault(), true);
+    EXPECT_EQ(psu.hasPgoodFault(), true);
+    EXPECT_EQ(psu.hasPSKillFault(), true);
+    EXPECT_EQ(psu.hasPS12VcsFault(), true);
+    EXPECT_EQ(psu.hasPSCS12VFault(), true);
+
+    // All faults cleared
+    expectations = {0};
+    setPMBusExpectations(mockPMBus, expectations);
+    // READ_VIN back in range.
+    EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
+        .Times(1)
+        .WillOnce(Return("206000"));
     EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.isPresent(), true);
@@ -1220,8 +1272,10 @@
     EXPECT_CALL(mockPMBus, readString(READ_VIN, _))
         .Times(1)
         .WillOnce(Return("201300"));
-    // Went from below minimum to within range, expect CLEAR_FAULTS.
-    EXPECT_CALL(mockPMBus, read(READ_VIN, _, _)).Times(1).WillOnce(Return(3));
+    // Went from below minimum to within range, expect clearVinUVFault().
+    EXPECT_CALL(mockPMBus, read("in1_lcrit_alarm", _, _))
+        .Times(1)
+        .WillOnce(Return(1));
     EXPECT_CALL(mockedUtil, setAvailable(_, _, true));
     psu.analyze();
     EXPECT_EQ(psu.hasVINUVFault(), false);