IpmbSensor: Add support for HSC sensor

1. Added support for HSC ADM1278 type sensors.
2. Added a subType as temp, volt, curr and power with default
   subtype as temperature.
3. Added support for Scale and offset with default value as
   scale = 1 and offset = 0.

Tested sensors with config file update.

Change-Id: If8c6b931bc9a27fabe4991ffce44b1a2545195ea
Signed-off-by: Vijay Khemka <vijaykhemka@fb.com>
diff --git a/include/IpmbSensor.hpp b/include/IpmbSensor.hpp
index 56e8b2f..e7a9540 100644
--- a/include/IpmbSensor.hpp
+++ b/include/IpmbSensor.hpp
@@ -12,9 +12,18 @@
     meSensor,
     PXE1410CVR,
     IR38363VR,
+    ADM1278HSC,
     mpsVR
 };
 
+enum class IpmbSubType
+{
+    temp,
+    curr,
+    power,
+    volt
+};
+
 struct IpmbSensor : public Sensor
 {
     IpmbSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
@@ -22,7 +31,7 @@
                const std::string& sensorConfiguration,
                sdbusplus::asio::object_server& objectServer,
                std::vector<thresholds::Threshold>&& thresholds,
-               uint8_t deviceAddress);
+               uint8_t deviceAddress, std::string& sensorTypeName);
     ~IpmbSensor();
 
     void checkThresholds(void) override;
@@ -32,6 +41,9 @@
     void runInitCmd(void);
 
     IpmbType type;
+    IpmbSubType subType;
+    double scaleVal;
+    double offsetVal;
     uint8_t commandAddress;
     uint8_t netfn;
     uint8_t command;
@@ -47,4 +59,4 @@
     sdbusplus::asio::object_server& objectServer;
     std::shared_ptr<sdbusplus::asio::connection> dbusConnection;
     boost::asio::deadline_timer waitTimer;
-};
\ No newline at end of file
+};
diff --git a/src/IpmbSensor.cpp b/src/IpmbSensor.cpp
index b3cc104..946c060 100644
--- a/src/IpmbSensor.cpp
+++ b/src/IpmbSensor.cpp
@@ -42,6 +42,8 @@
 static constexpr uint8_t meAddress = 1;
 static constexpr uint8_t lun = 0;
 
+static constexpr const char* sensorPathPrefix = "/xyz/openbmc_project/sensors/";
+
 using IpmbMethodType =
     std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>;
 
@@ -55,7 +57,7 @@
                        const std::string& sensorConfiguration,
                        sdbusplus::asio::object_server& objectServer,
                        std::vector<thresholds::Threshold>&& thresholdData,
-                       uint8_t deviceAddress) :
+                       uint8_t deviceAddress, std::string& sensorTypeName) :
     Sensor(boost::replace_all_copy(sensorName, " ", "_"),
            std::move(thresholdData), sensorConfiguration,
            "xyz.openbmc_project.Configuration.ExitAirTemp", ipmbMaxReading,
@@ -63,25 +65,23 @@
     objectServer(objectServer), dbusConnection(conn), waitTimer(io),
     deviceAddress(deviceAddress), readState(PowerState::on)
 {
+    std::string dbusPath = sensorPathPrefix + sensorTypeName + "/" + name;
+
     sensorInterface = objectServer.add_interface(
-        "/xyz/openbmc_project/sensors/temperature/" + name,
-        "xyz.openbmc_project.Sensor.Value");
+        dbusPath, "xyz.openbmc_project.Sensor.Value");
 
     if (thresholds::hasWarningInterface(thresholds))
     {
         thresholdInterfaceWarning = objectServer.add_interface(
-            "/xyz/openbmc_project/sensors/temperature/" + name,
-            "xyz.openbmc_project.Sensor.Threshold.Warning");
+            dbusPath, "xyz.openbmc_project.Sensor.Threshold.Warning");
     }
     if (thresholds::hasCriticalInterface(thresholds))
     {
         thresholdInterfaceCritical = objectServer.add_interface(
-            "/xyz/openbmc_project/sensors/temperature/" + name,
-            "xyz.openbmc_project.Sensor.Threshold.Critical");
+            dbusPath, "xyz.openbmc_project.Sensor.Threshold.Critical");
     }
-    association = objectServer.add_interface(
-        "/xyz/openbmc_project/sensors/temperature/" + name,
-        "org.openbmc.Associations");
+    association =
+        objectServer.add_interface(dbusPath, "org.openbmc.Associations");
     setupPowerMatch(conn);
 }
 
@@ -155,6 +155,33 @@
         commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
                        0x00, 0x00, 0x00, 0x01, 0x02, 0x8D};
     }
+    else if (type == IpmbType::ADM1278HSC)
+    {
+        commandAddress = meAddress;
+        switch (subType)
+        {
+            case IpmbSubType::temp:
+            case IpmbSubType::curr:
+                uint8_t snsNum;
+                if (subType == IpmbSubType::temp)
+                    snsNum = 0x8d;
+                else
+                    snsNum = 0x8c;
+                netfn = 0x2e;   // me bridge
+                command = 0xd9; // send raw pmbus
+                commandData = {0x57, 0x01, 0x00, 0x86, deviceAddress,
+                               0x00, 0x00, 0x01, 0x02, snsNum};
+                break;
+            case IpmbSubType::power:
+            case IpmbSubType::volt:
+                netfn = 0x4;    // sensor
+                command = 0x2d; // get sensor reading
+                commandData = {deviceAddress};
+                break;
+            default:
+                throw std::runtime_error("Invalid sensor type");
+        }
+    }
     else if (type == IpmbType::mpsVR)
     {
         commandAddress = meAddress;
@@ -234,7 +261,7 @@
                     }
                     std::cout << "\n";
                 }
-                uint16_t value = 0;
+                double value = 0;
                 if (type == IpmbType::meSensor)
                 {
                     if (data.empty())
@@ -267,6 +294,32 @@
                     // format based on the 11 bit linear data format
                     value = ((data[4] << 8) | data[3]) >> 3;
                 }
+                else if (type == IpmbType::ADM1278HSC)
+                {
+                    if (data.empty())
+                    {
+                        if (firstError)
+                        {
+                            std::cerr << "Invalid data from device: " << name
+                                      << "\n";
+                            firstError = false;
+                        }
+                        read();
+                        return;
+                    }
+                    switch (subType)
+                    {
+                        case IpmbSubType::temp:
+                        case IpmbSubType::curr:
+                            // format based on the 11 bit linear data format
+                            value = ((data[4] << 8) | data[3]);
+                            break;
+                        case IpmbSubType::power:
+                        case IpmbSubType::volt:
+                            value = data[0];
+                            break;
+                    }
+                }
                 else if (type == IpmbType::mpsVR)
                 {
                     if (data.size() < 4)
@@ -286,6 +339,9 @@
                 {
                     throw std::runtime_error("Invalid sensor type");
                 }
+
+                /* Adjust value as per scale and offset */
+                value = (value * scaleVal) + offsetVal;
                 updateValue(value);
                 read();
                 firstError = true; // success
@@ -336,10 +392,39 @@
 
                     std::string sensorClass =
                         loadVariant<std::string>(entry.second, "Class");
+
+                    /* Default sensor type is "temperature" */
+                    std::string sensorTypeName = "temperature";
+                    auto findType = entry.second.find("SensorType");
+                    if (findType != entry.second.end())
+                    {
+                        sensorTypeName = std::visit(VariantToStringVisitor(),
+                                                    findType->second);
+                    }
+
                     auto& sensor = sensors[name];
                     sensor = std::make_unique<IpmbSensor>(
                         dbusConnection, io, name, pathPair.first, objectServer,
-                        std::move(sensorThresholds), deviceAddress);
+                        std::move(sensorThresholds), deviceAddress,
+                        sensorTypeName);
+
+                    /* Initialize scale and offset value */
+                    sensor->scaleVal = 1;
+                    sensor->offsetVal = 0;
+
+                    auto findScaleVal = entry.second.find("ScaleValue");
+                    if (findScaleVal != entry.second.end())
+                    {
+                        sensor->scaleVal = std::visit(VariantToDoubleVisitor(),
+                                                      findScaleVal->second);
+                    }
+
+                    auto findOffsetVal = entry.second.find("OffsetValue");
+                    if (findOffsetVal != entry.second.end())
+                    {
+                        sensor->offsetVal = std::visit(VariantToDoubleVisitor(),
+                                                       findOffsetVal->second);
+                    }
 
                     auto findPowerState = entry.second.find("PowerState");
 
@@ -359,6 +444,10 @@
                     {
                         sensor->type = IpmbType::IR38363VR;
                     }
+                    else if (sensorClass == "HSCBridge")
+                    {
+                        sensor->type = IpmbType::ADM1278HSC;
+                    }
                     else if (sensorClass == "MpsBridgeTemp")
                     {
                         sensor->type = IpmbType::mpsVR;
@@ -372,6 +461,23 @@
                         std::cerr << "Invalid class " << sensorClass << "\n";
                         continue;
                     }
+
+                    if (sensorTypeName == "voltage")
+                    {
+                        sensor->subType = IpmbSubType::volt;
+                    }
+                    else if (sensorTypeName == "power")
+                    {
+                        sensor->subType = IpmbSubType::power;
+                    }
+                    else if (sensorTypeName == "current")
+                    {
+                        sensor->subType = IpmbSubType::curr;
+                    }
+                    else
+                    {
+                        sensor->subType = IpmbSubType::temp;
+                    }
                     sensor->init();
                 }
             }