i2c: Implement write function

Implement I2CDevice::write() by invoking i2c_smbus_write_xxx() APIs.
The code is referenced from i2c-tools' i2cset.c:

 https://github.com/ev3dev/i2c-tools/blob/ev3dev-stretch/tools/i2cset.c

Tested: Verify on Witherspoon that it writes the PSU unlock upgrade
        command and boot flag successfully.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I9fb014c787ef3ebb2f7793a0d012b1d652ef069f
diff --git a/tools/i2c/i2c.cpp b/tools/i2c/i2c.cpp
index da121d8..76756be 100644
--- a/tools/i2c/i2c.cpp
+++ b/tools/i2c/i2c.cpp
@@ -83,6 +83,52 @@
     }
 }
 
+void I2CDevice::checkWriteFuncs(int type)
+{
+    unsigned long funcs;
+
+    /* Check adapter functionality */
+    if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
+    {
+        throw I2CException("Failed to get funcs", busStr, devAddr, errno);
+    }
+
+    switch (type)
+    {
+        case I2C_SMBUS_BYTE:
+            if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE))
+            {
+                throw I2CException("Missing SMBUS_WRITE_BYTE", busStr, devAddr);
+            }
+            break;
+        case I2C_SMBUS_BYTE_DATA:
+            if (!(funcs & I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
+            {
+                throw I2CException("Missing SMBUS_WRITE_BYTE_DATA", busStr,
+                                   devAddr);
+            }
+            break;
+
+        case I2C_SMBUS_WORD_DATA:
+            if (!(funcs & I2C_FUNC_SMBUS_WRITE_WORD_DATA))
+            {
+                throw I2CException("Missing SMBUS_WRITE_WORD_DATA", busStr,
+                                   devAddr);
+            }
+            break;
+        case I2C_SMBUS_BLOCK_DATA:
+            if (!(funcs & I2C_FUNC_SMBUS_WRITE_BLOCK_DATA))
+            {
+                throw I2CException("Missing SMBUS_WRITE_BLOCK_DATA", busStr,
+                                   devAddr);
+            }
+            break;
+        default:
+            fprintf(stderr, "Unexpected read size type: %d\n", type);
+            assert(false);
+    }
+}
+
 void I2CDevice::read(uint8_t& data)
 {
     checkReadFuncs(I2C_SMBUS_BYTE);
@@ -132,30 +178,43 @@
 
 void I2CDevice::write(uint8_t data)
 {
-    // TODO
-    (void)data;
+    checkWriteFuncs(I2C_SMBUS_BYTE);
+
+    if (i2c_smbus_write_byte(fd, data) < 0)
+    {
+        throw I2CException("Failed to write byte", busStr, devAddr, errno);
+    }
 }
 
 void I2CDevice::write(uint8_t addr, uint8_t data)
 {
-    // TODO
-    (void)addr;
-    (void)data;
+    checkWriteFuncs(I2C_SMBUS_BYTE_DATA);
+
+    if (i2c_smbus_write_byte_data(fd, addr, data) < 0)
+    {
+        throw I2CException("Failed to write byte data", busStr, devAddr, errno);
+    }
 }
 
 void I2CDevice::write(uint8_t addr, uint16_t data)
 {
-    // TODO
-    (void)addr;
-    (void)data;
+    checkWriteFuncs(I2C_SMBUS_WORD_DATA);
+
+    if (i2c_smbus_write_word_data(fd, addr, data) < 0)
+    {
+        throw I2CException("Failed to write word data", busStr, devAddr, errno);
+    }
 }
 
 void I2CDevice::write(uint8_t addr, uint8_t size, const uint8_t* data)
 {
-    // TODO
-    (void)addr;
-    (void)size;
-    (void)data;
+    checkWriteFuncs(I2C_SMBUS_BLOCK_DATA);
+
+    if (i2c_smbus_write_block_data(fd, addr, size, data) < 0)
+    {
+        throw I2CException("Failed to write block data", busStr, devAddr,
+                           errno);
+    }
 }
 
 std::unique_ptr<I2CInterface> I2CDevice::create(uint8_t busId, uint8_t devAddr)
diff --git a/tools/i2c/i2c.hpp b/tools/i2c/i2c.hpp
index 7fe064f..befeaba 100644
--- a/tools/i2c/i2c.hpp
+++ b/tools/i2c/i2c.hpp
@@ -53,6 +53,17 @@
      */
     void checkReadFuncs(int type);
 
+    /** @brief Check i2c adapter write functionality
+     *
+     * Check if the i2c adapter has the functionality specified by the SMBus
+     * transaction type
+     *
+     * @param[in] type - The SMBus transaction type defined in linux/i2c.h
+     *
+     * @throw I2CException if the function is not supported
+     */
+    void checkWriteFuncs(int type);
+
   public:
     ~I2CDevice()
     {
diff --git a/tools/power-utils/test/test_updater.cpp b/tools/power-utils/test/test_updater.cpp
index d2dfd11..e1b6a2e 100644
--- a/tools/power-utils/test/test_updater.cpp
+++ b/tools/power-utils/test/test_updater.cpp
@@ -24,6 +24,7 @@
 
 using ::testing::_;
 using ::testing::An;
+using ::testing::Pointee;
 
 namespace updater
 {
@@ -95,10 +96,10 @@
     updater = std::make_unique<Updater>(psuInventoryPath, devPath, imageDir);
     updater->createI2CDevice();
     auto& i2c = getMockedI2c();
-    EXPECT_CALL(i2c, read(An<uint8_t&>()));
-    EXPECT_CALL(i2c, read(_, An<uint8_t&>()));
-    EXPECT_CALL(i2c, read(_, An<uint16_t&>()));
-    EXPECT_CALL(i2c, read(_, An<uint8_t&>(), _));
+
+    EXPECT_CALL(i2c, write(0xf0, 12, _));
+    EXPECT_CALL(i2c, write(0xf1, An<uint8_t>()));
+    EXPECT_CALL(i2c, read(0xf1, An<uint8_t&>()));
     updater->doUpdate();
 }
 
diff --git a/tools/power-utils/updater.cpp b/tools/power-utils/updater.cpp
index 2668030..4c6ed72 100644
--- a/tools/power-utils/updater.cpp
+++ b/tools/power-utils/updater.cpp
@@ -21,8 +21,10 @@
 #include "types.hpp"
 #include "utility.hpp"
 
+#include <chrono>
 #include <fstream>
 #include <phosphor-logging/log.hpp>
+#include <thread>
 
 using namespace phosphor::logging;
 namespace util = phosphor::power::util;
@@ -242,23 +244,26 @@
 
 int Updater::doUpdate()
 {
-    // TODO
-    uint8_t data;
-    uint8_t size;
-    uint16_t word;
-    std::vector<uint8_t> blockData(32);
+    using namespace std::chrono;
 
-    i2c->read(data);
-    printf("Read byte 0x%02x\n", data);
+    uint8_t data;
+    uint8_t unlockData[12] = {0x45, 0x43, 0x44, 0x31, 0x36, 0x30,
+                              0x33, 0x30, 0x30, 0x30, 0x34, 0x01};
+    uint8_t bootFlag = 0x01;
+    static_assert(sizeof(unlockData) == 12);
+
+    i2c->write(0xf0, sizeof(unlockData), unlockData);
+    printf("Unlock PSU\n");
+
+    std::this_thread::sleep_for(milliseconds(5));
+
+    i2c->write(0xf1, bootFlag);
+    printf("Set boot flag ret\n");
+
+    std::this_thread::sleep_for(seconds(3));
 
     i2c->read(0xf1, data);
-    printf("First read of 0x%02x, 0x%02x\n", 0xf1, data);
-
-    i2c->read(0xbd, word);
-    printf("Read word of 0x%02x, 0x%04x\n", 0xbd, word);
-
-    i2c->read(0x00, size, blockData.data()); // This throws on the device
-    printf("Read block data, size: %d\n", size);
+    printf("Read of 0x%02x, 0x%02x\n", 0xf1, data);
     return 0;
 }