regulators: Add PMBus utilities

Create namespace pmbus_utils.  This namespace contains utilities for
sending PMBus commands over an I2C interface.

These utilities are needed to implement the pmbus_write_vout_command and
pmbus_read_sensor actions from the JSON config file.

See pmbus_write_vout_command.md and pmbus_read_sensor.md for more
information on these actions.

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: I2fabca3ac8b9cd8b1f2b462c80feef6b6d7d60e8
diff --git a/phosphor-regulators/src/meson.build b/phosphor-regulators/src/meson.build
index d9734c3..816e74c 100644
--- a/phosphor-regulators/src/meson.build
+++ b/phosphor-regulators/src/meson.build
@@ -5,6 +5,7 @@
 
 phosphor_regulators_library_source_files = [
     'id_map.cpp',
+    'pmbus_utils.cpp',
 
     'actions/if_action.cpp',
     'actions/i2c_compare_bit_action.cpp',
diff --git a/phosphor-regulators/src/pmbus_utils.cpp b/phosphor-regulators/src/pmbus_utils.cpp
new file mode 100644
index 0000000..87a91b6
--- /dev/null
+++ b/phosphor-regulators/src/pmbus_utils.cpp
@@ -0,0 +1,61 @@
+/**
+ * Copyright © 2020 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 "pmbus_utils.hpp"
+
+namespace phosphor::power::regulators::pmbus_utils
+{
+
+void parseVoutMode(uint8_t voutModeValue, VoutDataFormat& format,
+                   int8_t& parameter)
+{
+    // Get the mode field from bits [6:5] in the VOUT_MODE value
+    uint8_t modeField = (voutModeValue & 0b0110'0000u) >> 5;
+
+    // Get data format from mode field
+    switch (modeField)
+    {
+        case 0b00u:
+            format = VoutDataFormat::linear;
+            break;
+        case 0b01u:
+            format = VoutDataFormat::vid;
+            break;
+        case 0b10u:
+            format = VoutDataFormat::direct;
+            break;
+        case 0b11u:
+            format = VoutDataFormat::ieee;
+            break;
+    }
+
+    // Get the parameter field from bits [4:0] in the VOUT_MODE value
+    uint8_t parameterField = voutModeValue & 0b0001'1111u;
+
+    // Get parameter value from parameter field
+    if (format == VoutDataFormat::linear)
+    {
+        // Extend sign bit if necessary because parameter is an exponent in
+        // two's complement format
+        if (parameterField & 0b0001'0000u)
+        {
+            parameterField |= 0b1110'0000u;
+        }
+    }
+    parameter = static_cast<int8_t>(parameterField);
+}
+
+} // namespace phosphor::power::regulators::pmbus_utils
diff --git a/phosphor-regulators/src/pmbus_utils.hpp b/phosphor-regulators/src/pmbus_utils.hpp
new file mode 100644
index 0000000..599c236
--- /dev/null
+++ b/phosphor-regulators/src/pmbus_utils.hpp
@@ -0,0 +1,117 @@
+/**
+ * Copyright © 2020 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.
+ */
+#pragma once
+
+#include <cmath>
+#include <cstdint>
+
+/**
+ * @namespace pmbus_utils
+ *
+ * Contains utilities for sending PMBus commands over an I2C interface.
+ */
+namespace phosphor::power::regulators::pmbus_utils
+{
+
+/*
+ * PMBus command codes.
+ *
+ * The constant names are all uppercase to match the PMBus documentation.
+ *
+ * Only the commands that are currently used by this application are defined.
+ * See the PMBus documentation for all valid command codes.
+ */
+const uint8_t VOUT_MODE{0x20u};
+const uint8_t VOUT_COMMAND{0x21u};
+
+/**
+ * Data formats for output voltage.
+ *
+ * These formats are used for commanding and reading output voltage and related
+ * parameters.
+ */
+enum class VoutDataFormat
+{
+    /**
+     * Linear scale that uses a two byte unsigned binary integer with a scaling
+     * factor.
+     */
+    linear,
+
+    /**
+     * Format that supports transmitting VID codes.
+     */
+    vid,
+
+    /**
+     * Direct format that uses an equation and device supplied coefficients.
+     */
+    direct,
+
+    /**
+     * Half-precision floating point format that follows the IEEE-754 standard
+     * for representing magnitudes in 16 bits.
+     */
+    ieee
+};
+
+/**
+ * Parse the one byte value of the VOUT_MODE command.
+ *
+ * VOUT_MODE contains a 'mode' field that indicates the data format used for
+ * output voltage values.
+ *
+ * VOUT_MODE also contains a 'parameter' field whose value is dependent on the
+ * data format:
+ *  - Linear format: value is an exponent
+ *  - VID format: value is a VID code
+ *  - IEEE and Direct formats: value is not used
+ *
+ * @param voutModeValue one byte value of VOUT_MODE command
+ * @param format data format from the 'mode' field
+ * @param parameter parameter value from the 'parameter' field
+ */
+void parseVoutMode(uint8_t voutModeValue, VoutDataFormat& format,
+                   int8_t& parameter);
+
+/**
+ * Converts a volts value to the linear data format for output voltage.
+ *
+ * This data format consists of the following:
+ *   - Two byte value
+ *   - 16-bit unsigned mantissa value stored in the two bytes
+ *   - 5-bit signed exponent value that is not stored in the two bytes
+ *
+ * The exponent value is typically obtained from the PMBus VOUT_MODE command
+ * or from the hardware device documentation (data sheet).
+ *
+ * Note that this format differs from the linear data format for values
+ * unrelated to output voltage.
+ *
+ * @param volts volts value to convert; must not be negative
+ * @param exponent 5-bit signed exponent used to convert value
+ * @return linear data format value
+ */
+inline uint16_t convertToVoutLinear(double volts, int8_t exponent)
+{
+    // Obtain mantissa using equation 'mantissa = volts / 2^exponent'
+    double mantissa = volts / std::pow(2.0, static_cast<double>(exponent));
+
+    // Return the mantissa value after converting to a rounded uint16_t
+    return static_cast<uint16_t>(std::lround(mantissa));
+}
+
+} // namespace phosphor::power::regulators::pmbus_utils
diff --git a/phosphor-regulators/test/meson.build b/phosphor-regulators/test/meson.build
index 707bc83..78661c5 100644
--- a/phosphor-regulators/test/meson.build
+++ b/phosphor-regulators/test/meson.build
@@ -6,6 +6,7 @@
 phosphor_regulators_tests_source_files = [
     'device_tests.cpp',
     'id_map_tests.cpp',
+    'pmbus_utils_tests.cpp',
     'rail_tests.cpp',
     'rule_tests.cpp',
 
diff --git a/phosphor-regulators/test/pmbus_utils_tests.cpp b/phosphor-regulators/test/pmbus_utils_tests.cpp
new file mode 100644
index 0000000..8c28efd
--- /dev/null
+++ b/phosphor-regulators/test/pmbus_utils_tests.cpp
@@ -0,0 +1,143 @@
+/**
+ * Copyright © 2020 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 "pmbus_utils.hpp"
+
+#include <cstdint>
+
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::regulators;
+
+TEST(PMBusUtilsTests, ParseVoutMode)
+{
+    uint8_t voutModeValue;
+    pmbus_utils::VoutDataFormat format;
+    int8_t parameter;
+
+    // Linear format: Exponent is negative: 0b1'1111
+    voutModeValue = 0b0001'1111u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::linear);
+    EXPECT_EQ(parameter, -1);
+
+    // Linear format: Exponent is negative: 0b1'0000
+    voutModeValue = 0b1001'0000u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::linear);
+    EXPECT_EQ(parameter, -16);
+
+    // Linear format: Exponent is positive: 0b0'1111
+    voutModeValue = 0b1000'1111u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::linear);
+    EXPECT_EQ(parameter, 15);
+
+    // Linear format: Exponent is positive: 0b0'0001
+    voutModeValue = 0b0000'0001u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::linear);
+    EXPECT_EQ(parameter, 1);
+
+    // Linear format: Exponent is zero: 0b0'0000
+    voutModeValue = 0b0000'0000u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::linear);
+    EXPECT_EQ(parameter, 0);
+
+    // VID format: VID code is 0b1'1111
+    voutModeValue = 0b0011'1111u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::vid);
+    EXPECT_EQ(parameter, 31);
+
+    // VID format: VID code is 0b1'0000
+    voutModeValue = 0b1011'0000u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::vid);
+    EXPECT_EQ(parameter, 16);
+
+    // VID format: VID code is 0b0'1111
+    voutModeValue = 0b1010'1111u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::vid);
+    EXPECT_EQ(parameter, 15);
+
+    // VID format: VID code is 0b0'0001
+    voutModeValue = 0b0010'0001u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::vid);
+    EXPECT_EQ(parameter, 1);
+
+    // VID format: VID code is 0b0'0000
+    voutModeValue = 0b1010'0000u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::vid);
+    EXPECT_EQ(parameter, 0);
+
+    // Direct format
+    voutModeValue = 0b1100'0000u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::direct);
+    EXPECT_EQ(parameter, 0);
+
+    // IEEE format
+    voutModeValue = 0b0110'0000u;
+    pmbus_utils::parseVoutMode(voutModeValue, format, parameter);
+    EXPECT_EQ(format, pmbus_utils::VoutDataFormat::ieee);
+    EXPECT_EQ(parameter, 0);
+}
+
+TEST(PMBusUtilsTests, ConvertToVoutLinear)
+{
+    double volts;
+    int8_t exponent;
+
+    // Exponent > 0: Value is not rounded up
+    volts = 13.9;
+    exponent = 2;
+    // 13.9 / 2^2 == 3.475 = 3
+    EXPECT_EQ(pmbus_utils::convertToVoutLinear(volts, exponent), 3);
+
+    // Exponent > 0: Value is rounded up
+    volts = 14.0;
+    exponent = 2;
+    // 14.0 / 2^2 == 3.5 = 4
+    EXPECT_EQ(pmbus_utils::convertToVoutLinear(volts, exponent), 4);
+
+    // Exponent = 0: Value is not rounded up
+    volts = 2.49;
+    exponent = 0;
+    // 2.49 / 2^0 == 2.49 = 2
+    EXPECT_EQ(pmbus_utils::convertToVoutLinear(volts, exponent), 2);
+
+    // Exponent = 0: Value is rounded up
+    volts = 2.51;
+    exponent = 0;
+    // 2.51 / 2^0 == 2.51 = 3
+    EXPECT_EQ(pmbus_utils::convertToVoutLinear(volts, exponent), 3);
+
+    // Exponent < 0: Value is not rounded up
+    volts = 1.32613;
+    exponent = -8;
+    // 1.32613 / 2^-8 == 339.48928 == 339
+    EXPECT_EQ(pmbus_utils::convertToVoutLinear(volts, exponent), 339);
+
+    // Exponent < 0: Value is rounded up
+    volts = 1.32618;
+    exponent = -8;
+    // 1.32618 / 2^-8 == 339.50208 == 340
+    EXPECT_EQ(pmbus_utils::convertToVoutLinear(volts, exponent), 340);
+}