tools: Initially add i2c tool and mock

The power-utils will invoke i2c get/set commands to the i2c device for
PSU code update.
Create a separated i2c tool because it is logically standalone and could
be shared for other utils.
Also add an I2CInterface and its mock to make it easier to test when
developing i2c related operations.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: If5383547e6d84c0a79610e2d2d6f2fa8ee9dc061
diff --git a/meson.build b/meson.build
index 8567052..d79ba5e 100644
--- a/meson.build
+++ b/meson.build
@@ -79,5 +79,6 @@
 subdir('phosphor-regulators')
 subdir('power-sequencer')
 subdir('power-supply')
+subdir('tools/i2c')
 subdir('tools/power-utils')
 subdir('test')
diff --git a/tools/i2c/i2c.cpp b/tools/i2c/i2c.cpp
new file mode 100644
index 0000000..fdf0732
--- /dev/null
+++ b/tools/i2c/i2c.cpp
@@ -0,0 +1,73 @@
+#include "i2c.hpp"
+
+namespace i2c
+{
+
+void I2CDevice::read(uint8_t& data)
+{
+    // TODO
+    (void)data;
+}
+
+void I2CDevice::read(uint8_t addr, uint8_t& data)
+{
+    // TODO
+    (void)addr;
+    (void)data;
+}
+
+void I2CDevice::read(uint8_t addr, uint16_t& data)
+{
+    // TODO
+    (void)addr;
+    (void)data;
+}
+
+void I2CDevice::read(uint8_t addr, uint8_t& size, uint8_t* data)
+{
+    // TODO
+    (void)addr;
+    (void)size;
+    (void)data;
+}
+
+void I2CDevice::write(uint8_t data)
+{
+    // TODO
+    (void)data;
+}
+
+void I2CDevice::write(uint8_t addr, uint8_t data)
+{
+    // TODO
+    (void)addr;
+    (void)data;
+}
+
+void I2CDevice::write(uint8_t addr, uint16_t data)
+{
+    // TODO
+    (void)addr;
+    (void)data;
+}
+
+void I2CDevice::write(uint8_t addr, uint8_t size, const uint8_t* data)
+{
+    // TODO
+    (void)addr;
+    (void)size;
+    (void)data;
+}
+
+std::unique_ptr<I2CInterface> I2CDevice::create(uint8_t busId, uint8_t devAddr)
+{
+    std::unique_ptr<I2CDevice> dev(new I2CDevice(busId, devAddr));
+    return dev;
+}
+
+std::unique_ptr<I2CInterface> create(uint8_t busId, uint8_t devAddr)
+{
+    return I2CDevice::create(busId, devAddr);
+}
+
+} // namespace i2c
diff --git a/tools/i2c/i2c.hpp b/tools/i2c/i2c.hpp
new file mode 100644
index 0000000..f9cc09e
--- /dev/null
+++ b/tools/i2c/i2c.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "i2c_interface.hpp"
+
+namespace i2c
+{
+
+class I2CDevice : public I2CInterface
+{
+  private:
+    I2CDevice() = delete;
+
+    explicit I2CDevice(uint8_t busId, uint8_t devAddr)
+    {
+        // TODO
+        (void)busId;
+        (void)devAddr;
+    }
+
+  public:
+    virtual ~I2CDevice() = default;
+
+    /** @copydoc I2CInterface::read(uint8_t&) */
+    void read(uint8_t& data) override;
+
+    /** @copydoc I2CInterface::read(uint8_t,uint8_t&) */
+    void read(uint8_t addr, uint8_t& data) override;
+
+    /** @copydoc I2CInterface::read(uint8_t,uint16_t&) */
+    void read(uint8_t addr, uint16_t& data) override;
+
+    /** @copydoc I2CInterface::read(uint8_t,uint8_t&,uint8_t*) */
+    void read(uint8_t addr, uint8_t& size, uint8_t* data) override;
+
+    /** @copydoc I2CInterface::write(uint8_t) */
+    void write(uint8_t data) override;
+
+    /** @copydoc I2CInterface::write(uint8_t,uint8_t) */
+    void write(uint8_t addr, uint8_t data) override;
+
+    /** @copydoc I2CInterface::write(uint8_t,uint16_t) */
+    void write(uint8_t addr, uint16_t data) override;
+
+    /** @copydoc I2CInterface::write(uint8_t,uint8_t,const uint8_t*) */
+    void write(uint8_t addr, uint8_t size, const uint8_t* data) override;
+
+    /** @brief Create an I2CInterface instance
+     *
+     * @param[in] busId - The i2c bus ID
+     * @param[in] devAddr - The device address of the i2c
+     *
+     * @return The unique_ptr holding the I2CInterface
+     */
+    static std::unique_ptr<I2CInterface> create(uint8_t busId, uint8_t devAddr);
+};
+
+} // namespace i2c
diff --git a/tools/i2c/i2c_interface.hpp b/tools/i2c/i2c_interface.hpp
new file mode 100644
index 0000000..ab12e32
--- /dev/null
+++ b/tools/i2c/i2c_interface.hpp
@@ -0,0 +1,136 @@
+#pragma once
+
+#include <cstdint>
+#include <cstring>
+#include <exception>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace i2c
+{
+
+class I2CException : public std::exception
+{
+  public:
+    explicit I2CException(const std::string& info, const std::string& bus,
+                          uint8_t addr, int errorCode = 0) :
+        bus(bus),
+        addr(addr), errorCode(errorCode)
+    {
+        std::stringstream ss;
+        ss << "I2CException: " << info << ": bus " << bus << ", addr 0x"
+           << std::hex << static_cast<int>(addr);
+        if (errorCode != 0)
+        {
+            ss << std::dec << ", errno " << errorCode << ": "
+               << strerror(errorCode);
+        }
+        errStr = ss.str();
+    }
+    virtual ~I2CException() = default;
+
+    const char* what() const noexcept override
+    {
+        return errStr.c_str();
+    }
+    std::string bus;
+    uint8_t addr;
+    int errorCode;
+    std::string errStr;
+};
+
+class I2CInterface
+{
+  public:
+    virtual ~I2CInterface() = default;
+
+    /** @brief Read byte data from i2c
+     *
+     * @param[out] data - The data read from the i2c device
+     *
+     * @throw I2CException on error
+     */
+    virtual void read(uint8_t& data) = 0;
+
+    /** @brief Read byte data from i2c
+     *
+     * @param[in] addr - The register address of the i2c device
+     * @param[out] data - The data read from the i2c device
+     *
+     * @throw I2CException on error
+     */
+    virtual void read(uint8_t addr, uint8_t& data) = 0;
+
+    /** @brief Read word data from i2c
+     *
+     * @param[in] addr - The register address of the i2c device
+     * @param[out] data - The data read from the i2c device
+     *
+     * @throw I2CException on error
+     */
+    virtual void read(uint8_t addr, uint16_t& data) = 0;
+
+    /** @brief Read block data from i2c
+     *
+     * @param[in] addr - The register address of the i2c device
+     * @param[out] size - The size of data read from i2c device
+     * @param[out] data - The pointer to buffer to read from the i2c device,
+     *                    the buffer shall be big enough to hold the data
+     *                    returned by the device. SMBus allows at most 32
+     *                    bytes.
+     *
+     * @throw I2CException on error
+     */
+    virtual void read(uint8_t addr, uint8_t& size, uint8_t* data) = 0;
+
+    /** @brief Write byte data to i2c
+     *
+     * @param[in] data - The data to write to the i2c device
+     *
+     * @throw I2CException on error
+     */
+    virtual void write(uint8_t data) = 0;
+
+    /** @brief Write byte data to i2c
+     *
+     * @param[in] addr - The register address of the i2c device
+     * @param[in] data - The data to write to the i2c device
+     *
+     * @throw I2CException on error
+     */
+    virtual void write(uint8_t addr, uint8_t data) = 0;
+
+    /** @brief Write word data to i2c
+     *
+     * @param[in] addr - The register address of the i2c device
+     * @param[in] data - The data to write to the i2c device
+     *
+     * @throw I2CException on error
+     */
+    virtual void write(uint8_t addr, uint16_t data) = 0;
+
+    /** @brief Write block data to i2c
+     *
+     * @param[in] addr - The register address of the i2c device
+     * @param[in] size - The size of data to write, SMBus allows at most 32
+     *                   bytes
+     * @param[in] data - The data to write to the i2c device
+     *
+     * @throw I2CException on error
+     */
+    virtual void write(uint8_t addr, uint8_t size, const uint8_t* data) = 0;
+};
+
+/** @brief Create an I2CInterface instance
+ *
+ * @param[in] busId - The i2c bus ID
+ * @param[in] devAddr - The device address of the i2c
+ *
+ * @return The unique_ptr holding the I2CInterface
+ */
+std::unique_ptr<I2CInterface> create(uint8_t busId, uint8_t devAddr);
+
+} // namespace i2c
diff --git a/tools/i2c/meson.build b/tools/i2c/meson.build
new file mode 100644
index 0000000..1f4e7d1
--- /dev/null
+++ b/tools/i2c/meson.build
@@ -0,0 +1,5 @@
+libi2c_dev = static_library(
+    'i2c_dev',
+    'i2c.cpp',
+    link_args : '-li2c',
+)
diff --git a/tools/i2c/test/mocked_i2c_interface.hpp b/tools/i2c/test/mocked_i2c_interface.hpp
new file mode 100644
index 0000000..feb8f74
--- /dev/null
+++ b/tools/i2c/test/mocked_i2c_interface.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "../i2c_interface.hpp"
+
+#include <gmock/gmock.h>
+
+namespace i2c
+{
+
+class MockedI2CInterface : public I2CInterface
+{
+  public:
+    virtual ~MockedI2CInterface() = default;
+
+    MOCK_METHOD(void, read, (uint8_t & data), (override));
+    MOCK_METHOD(void, read, (uint8_t addr, uint8_t& data), (override));
+    MOCK_METHOD(void, read, (uint8_t addr, uint16_t& data), (override));
+    MOCK_METHOD(void, read, (uint8_t addr, uint8_t& size, uint8_t* data),
+                (override));
+
+    MOCK_METHOD(void, write, (uint8_t data), (override));
+    MOCK_METHOD(void, write, (uint8_t addr, uint8_t data), (override));
+    MOCK_METHOD(void, write, (uint8_t addr, uint16_t data), (override));
+    MOCK_METHOD(void, write, (uint8_t addr, uint8_t size, const uint8_t* data),
+                (override));
+};
+
+std::unique_ptr<I2CInterface> create(uint8_t /*busId*/, uint8_t /*devAddr*/)
+{
+    return std::make_unique<MockedI2CInterface>();
+}
+
+} // namespace i2c