i2c: Add i2c block transaction support

The previous code always uses SMBus block read/write.
On some PSU (e.g. FP5280G2's PSU) the I2C block read/write is
required, so add that support.

Specifically, add a Mode enum class and add the parameter for block
read/write to indicate whether SMBus or I2C block read/write is to be
called.

Tested: Verify the code works on FP5280G2 with I2C block write.

Note: Currently there is no case for I2C block read, so that function is
      not tested.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I5f77ffe6900d14f3703dae7241799a7b37c5a726
diff --git a/tools/i2c/i2c.cpp b/tools/i2c/i2c.cpp
index 76756be..624bc60 100644
--- a/tools/i2c/i2c.cpp
+++ b/tools/i2c/i2c.cpp
@@ -76,6 +76,13 @@
                                    devAddr);
             }
             break;
+        case I2C_SMBUS_I2C_BLOCK_DATA:
+            if (!(funcs & I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+            {
+                throw I2CException("Missing I2C_FUNC_SMBUS_READ_I2C_BLOCK",
+                                   busStr, devAddr);
+            }
+            break;
         default:
             fprintf(stderr, "Unexpected read size type: %d\n", type);
             assert(false);
@@ -123,6 +130,13 @@
                                    devAddr);
             }
             break;
+        case I2C_SMBUS_I2C_BLOCK_DATA:
+            if (!(funcs & I2C_FUNC_SMBUS_WRITE_I2C_BLOCK))
+            {
+                throw I2CException("Missing I2C_FUNC_SMBUS_WRITE_I2C_BLOCK",
+                                   busStr, devAddr);
+            }
+            break;
         default:
             fprintf(stderr, "Unexpected read size type: %d\n", type);
             assert(false);
@@ -164,11 +178,25 @@
     data = static_cast<uint16_t>(ret);
 }
 
-void I2CDevice::read(uint8_t addr, uint8_t& size, uint8_t* data)
+void I2CDevice::read(uint8_t addr, uint8_t& size, uint8_t* data, Mode mode)
 {
-    checkReadFuncs(I2C_SMBUS_BLOCK_DATA);
-
-    int ret = i2c_smbus_read_block_data(fd, addr, data);
+    int ret;
+    switch (mode)
+    {
+        case Mode::SMBUS:
+            checkReadFuncs(I2C_SMBUS_BLOCK_DATA);
+            ret = i2c_smbus_read_block_data(fd, addr, data);
+            break;
+        case Mode::I2C:
+            checkReadFuncs(I2C_SMBUS_I2C_BLOCK_DATA);
+            ret = i2c_smbus_read_i2c_block_data(fd, addr, size, data);
+            if (ret != size)
+            {
+                throw I2CException("Failed to read i2c block data", busStr,
+                                   devAddr, errno);
+            }
+            break;
+    }
     if (ret < 0)
     {
         throw I2CException("Failed to read block data", busStr, devAddr, errno);
@@ -206,11 +234,22 @@
     }
 }
 
-void I2CDevice::write(uint8_t addr, uint8_t size, const uint8_t* data)
+void I2CDevice::write(uint8_t addr, uint8_t size, const uint8_t* data,
+                      Mode mode)
 {
-    checkWriteFuncs(I2C_SMBUS_BLOCK_DATA);
-
-    if (i2c_smbus_write_block_data(fd, addr, size, data) < 0)
+    int ret;
+    switch (mode)
+    {
+        case Mode::SMBUS:
+            checkWriteFuncs(I2C_SMBUS_BLOCK_DATA);
+            ret = i2c_smbus_write_block_data(fd, addr, size, data);
+            break;
+        case Mode::I2C:
+            checkWriteFuncs(I2C_SMBUS_I2C_BLOCK_DATA);
+            ret = i2c_smbus_write_i2c_block_data(fd, addr, size, data);
+            break;
+    }
+    if (ret < 0)
     {
         throw I2CException("Failed to write block data", busStr, devAddr,
                            errno);
diff --git a/tools/i2c/i2c.hpp b/tools/i2c/i2c.hpp
index befeaba..05b02b5 100644
--- a/tools/i2c/i2c.hpp
+++ b/tools/i2c/i2c.hpp
@@ -79,8 +79,9 @@
     /** @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::read(uint8_t,uint8_t&,uint8_t*,Mode) */
+    void read(uint8_t addr, uint8_t& size, uint8_t* data,
+              Mode mode = Mode::SMBUS) override;
 
     /** @copydoc I2CInterface::write(uint8_t) */
     void write(uint8_t data) override;
@@ -91,8 +92,9 @@
     /** @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;
+    /** @copydoc I2CInterface::write(uint8_t,uint8_t,const uint8_t*,Mode) */
+    void write(uint8_t addr, uint8_t size, const uint8_t* data,
+               Mode mode = Mode::SMBUS) override;
 
     /** @brief Create an I2CInterface instance
      *
diff --git a/tools/i2c/i2c_interface.hpp b/tools/i2c/i2c_interface.hpp
index ab12e32..152f1e9 100644
--- a/tools/i2c/i2c_interface.hpp
+++ b/tools/i2c/i2c_interface.hpp
@@ -47,6 +47,13 @@
   public:
     virtual ~I2CInterface() = default;
 
+    /** @brief The block transaction mode */
+    enum class Mode
+    {
+        SMBUS,
+        I2C,
+    };
+
     /** @brief Read byte data from i2c
      *
      * @param[out] data - The data read from the i2c device
@@ -76,15 +83,22 @@
     /** @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[in,out] size - The out parameter to indicate the size of data read
+     *                       from i2c device; when mode is I2C, it is also the
+     *                       input parameter to indicate how many bytes to read
      * @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.
+     * @param[in] mode - The block read mode, either SMBus or I2C.
+     *                   Internally, it invokes functions from libi2c:
+     *                    * For SMBus: i2c_smbus_read_block_data()
+     *                    * For I2C: i2c_smbus_read_i2c_block_data()
      *
      * @throw I2CException on error
      */
-    virtual void read(uint8_t addr, uint8_t& size, uint8_t* data) = 0;
+    virtual void read(uint8_t addr, uint8_t& size, uint8_t* data,
+                      Mode mode = Mode::SMBUS) = 0;
 
     /** @brief Write byte data to i2c
      *
@@ -118,10 +132,15 @@
      * @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
+     * @param[in] mode - The block write mode, either SMBus or I2C.
+     *                   Internally, it invokes functions from libi2c:
+     *                    * For SMBus: i2c_smbus_write_block_data()
+     *                    * For I2C: i2c_smbus_write_i2c_block_data()
      *
      * @throw I2CException on error
      */
-    virtual void write(uint8_t addr, uint8_t size, const uint8_t* data) = 0;
+    virtual void write(uint8_t addr, uint8_t size, const uint8_t* data,
+                       Mode mode = Mode::SMBUS) = 0;
 };
 
 /** @brief Create an I2CInterface instance
diff --git a/tools/i2c/test/mocked_i2c_interface.hpp b/tools/i2c/test/mocked_i2c_interface.hpp
index feb8f74..5bb0a3a 100644
--- a/tools/i2c/test/mocked_i2c_interface.hpp
+++ b/tools/i2c/test/mocked_i2c_interface.hpp
@@ -15,13 +15,15 @@
     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),
+    MOCK_METHOD(void, read,
+                (uint8_t addr, uint8_t& size, uint8_t* data, Mode mode),
                 (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),
+    MOCK_METHOD(void, write,
+                (uint8_t addr, uint8_t size, const uint8_t* data, Mode mode),
                 (override));
 };
 
diff --git a/tools/power-utils/test/test_updater.cpp b/tools/power-utils/test/test_updater.cpp
index e1b6a2e..c51c233 100644
--- a/tools/power-utils/test/test_updater.cpp
+++ b/tools/power-utils/test/test_updater.cpp
@@ -97,7 +97,7 @@
     updater->createI2CDevice();
     auto& i2c = getMockedI2c();
 
-    EXPECT_CALL(i2c, write(0xf0, 12, _));
+    EXPECT_CALL(i2c, write(0xf0, 12, _, I2CInterface::Mode::SMBUS));
     EXPECT_CALL(i2c, write(0xf1, An<uint8_t>()));
     EXPECT_CALL(i2c, read(0xf1, An<uint8_t&>()));
     updater->doUpdate();