pw_i2c: Add I2cRegisterDevice and a demo file

- Add I2cRegisterDevice
- Add RegisterModification
- Add a simple demo app that does read/write to the STM board

Change-Id: If0efddc0c09e72f460848240d977827ceaf06eec
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/15669
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/modules.gni b/modules.gni
index 915a30d..956ca20 100644
--- a/modules.gni
+++ b/modules.gni
@@ -35,6 +35,9 @@
   dir_pw_docgen = get_path_info("pw_docgen", "abspath")
   dir_pw_doctor = get_path_info("pw_doctor", "abspath")
   dir_pw_hex_dump = get_path_info("pw_hex_dump", "abspath")
+  dir_pw_i2c = get_path_info("pw_i2c", "abspath")
+  dir_pw_i2c_baremetal_stm32f429 =
+      get_path_info("pw_i2c_baremetal_stm32f429", "abspath")
   dir_pw_env_setup = get_path_info("pw_env_setup", "abspath")
   dir_pw_hdlc_lite = get_path_info("pw_hdlc_lite", "abspath")
   dir_pw_kvs = get_path_info("pw_kvs", "abspath")
diff --git a/pw_bytes/BUILD.gn b/pw_bytes/BUILD.gn
index d211198..38c7f26 100644
--- a/pw_bytes/BUILD.gn
+++ b/pw_bytes/BUILD.gn
@@ -28,8 +28,8 @@
   public_configs = [ ":default_config" ]
   public = [
     "public/pw_bytes/byte_builder.h",
-    "public/pw_bytes/span.h",
     "public/pw_bytes/endianness.h",
+    "public/pw_bytes/span.h",
   ]
   sources = [ "byte_builder.cc" ]
   public_deps = [
diff --git a/pw_bytes/public/pw_bytes/endianness.h b/pw_bytes/public/pw_bytes/endianness.h
index 69da843..496401b 100644
--- a/pw_bytes/public/pw_bytes/endianness.h
+++ b/pw_bytes/public/pw_bytes/endianness.h
@@ -30,7 +30,7 @@
     return sizeof(uint16_t);
   } else if (int_size == IntSize::kUint32) {
     return sizeof(uint32_t);
-  } else if (int_size == IntSize::kUint64){
+  } else if (int_size == IntSize::kUint64) {
     return sizeof(uint64_t);
   } else {
     return 0;  // Indicate an error
diff --git a/pw_i2c/BUILD b/pw_i2c/BUILD
index fdf7a6b..fa4ebe2 100644
--- a/pw_i2c/BUILD
+++ b/pw_i2c/BUILD
@@ -26,12 +26,13 @@
     name = "pw_i2c",
     hdrs = [
         "public/pw_i2c/i2c_bus.h",
-        #"public/pw_i2c/i2c_device.h",
+        "public/pw_i2c/i2c_register_device.h",
     ],
     includes = ["public"],
     deps = [
         "//pw_preprocessor",
         "//pw_span",
         "//pw_bytes",
+        "//pw_assert",
     ],
 )
diff --git a/pw_i2c/BUILD.gn b/pw_i2c/BUILD.gn
index cb2e67f..17a0f97 100644
--- a/pw_i2c/BUILD.gn
+++ b/pw_i2c/BUILD.gn
@@ -16,9 +16,8 @@
 import("//build_overrides/pigweed.gni")
 
 import("$dir_pw_bloat/bloat.gni")
-import("$dir_pw_unit_test/test.gni")
 import("$dir_pw_build/target_types.gni")
-
+import("$dir_pw_docgen/docs.gni")
 config("default_config") {
   include_dirs = [ "public" ]
 }
@@ -27,10 +26,17 @@
   public_configs = [ ":default_config" ]
   public = [
     "public/pw_i2c/i2c_bus.h",
+    "public/pw_i2c/i2c_register_device.h",
   ]
   public_deps = [
-    dir_pw_preprocessor,
+    dir_pw_assert,
     dir_pw_bytes,
+    dir_pw_bytes,
+    dir_pw_preprocessor,
     dir_pw_status,
   ]
 }
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_i2c/public/pw_i2c/i2c_bus.h b/pw_i2c/public/pw_i2c/i2c_bus.h
index 877c523..fa1d3a8 100644
--- a/pw_i2c/public/pw_i2c/i2c_bus.h
+++ b/pw_i2c/public/pw_i2c/i2c_bus.h
@@ -15,6 +15,7 @@
 
 #include <cinttypes>
 #include <cstddef>
+
 #include "pw_bytes/span.h"
 #include "pw_status/status.h"
 
@@ -33,10 +34,34 @@
 
   virtual pw::Status Disable() = 0;
 
-  // TODO: A timeout or a deadline should be added here later
+  // Write bytes in tx_buffer to the I2C bus, the read rx_buffer.size() bytes
+  // from the bus. Only write if rx_buffer is empty; only read if tx_buffer
+  // is empty
+  //
+  // Effect on the wire of this API:
+  // 1) Generate start condition
+  // If tx_buffer non-empty:
+  //   2) output the address with R/W bit 0, must be ack'd
+  //   3) the write data where each byte must be ack'd
+  // If rx_buffer non-empty:
+  //   4) output the address with R/W bit 1, must be ack'd
+  //   5) device is expected to send the entire rx buffer's worth of bytes
+  //      where the master/controller must ack each and nack the last
+  // 6) Generate Stop condition
+  // TODO: A timeout or a deadline should be added to the API later
   virtual pw::Status WriteRead(I2cAddress address,
                                ConstByteSpan tx_buffer,
                                ByteSpan rx_buffer) = 0;
+
+  pw::Status Write(I2cAddress address, ConstByteSpan tx_buffer) {
+    ByteSpan empty_span{};
+    return WriteRead(address, tx_buffer, empty_span);
+  }
+
+  pw::Status Read(I2cAddress address, ByteSpan rx_buffer) {
+    ByteSpan empty_span{};
+    return WriteRead(address, empty_span, rx_buffer);
+  }
 };
 
-}
+}  // namespace pw::i2c
diff --git a/pw_i2c/public/pw_i2c/i2c_register_device.h b/pw_i2c/public/pw_i2c/i2c_register_device.h
new file mode 100644
index 0000000..7383c5d
--- /dev/null
+++ b/pw_i2c/public/pw_i2c/i2c_register_device.h
@@ -0,0 +1,269 @@
+// Copyright 2020 The Pigweed Authors
+//
+// 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
+//
+//     https://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 <cstddef>
+
+#include "pw_assert/assert.h"
+#include "pw_bytes/endianness.h"
+#include "pw_i2c/i2c_bus.h"
+#include "pw_preprocessor/util.h"
+
+namespace pw::i2c {
+
+// Represents an I2C slave register device.
+// Instead of using a template class, command_type and register_type are taken
+// as parameters during the construction time. By doing this, the object is
+// evaluated in run-time instead of compile time
+// enum class IntSize { kUint8, kUint16, kUint32, kUint64 };
+
+// All values to read/write will be passed as pointer.
+// Check demo_app_main.cc for sample usage
+class I2cRegisterDevice {
+ public:
+  constexpr I2cRegisterDevice(I2cBus& i2c_bus,
+                              I2cAddress i2c_addr,
+                              pw::IntSize command_type,
+                              pw::IntSize register_type,
+                              pw::ByteOrder kEndianness)
+      : command_type(command_type),
+        register_type(register_type),
+        kEndianness(kEndianness),
+        i2c_bus_(i2c_bus),
+        i2c_addr_(i2c_addr) {}
+
+  // Write the command to the I2C bus, then read rx_buffer.size() bytes from
+  // the bus.
+  pw::Status Read(void* command, std::span<std::byte> rx_buffer) {
+    const size_t command_size = pw::GetIntSize(command_type);
+    std::byte command_buffer[command_size];
+
+    pw::ConvertToEndian(command,
+                        ByteSpan(command_buffer, command_size),
+                        command_type,
+                        kEndianness);
+
+    return i2c_bus_.WriteRead(
+        i2c_addr_, std::span(command_buffer, command_size), rx_buffer);
+  }
+
+  // Read the value in register_id into rx_buffer
+  pw::Status ReadRegister(void* register_id, void* rx_buffer) {
+    const size_t register_size = pw::GetIntSize(register_type);
+    std::byte return_buffer[register_size];
+
+    pw::Status read_status =
+        Read(register_id, std::span(return_buffer, register_size));
+
+    if (!read_status.ok()) {
+      return read_status;
+    }
+
+    pw::GetEndian(ConstByteSpan(return_buffer, register_size),
+                  rx_buffer,
+                  register_type,
+                  kEndianness);
+
+    return pw::Status::OK;
+  }
+
+  // Write tx_buffer to the I2C bus.
+  pw::Status Write(std::span<const std::byte> tx_buffer) {
+    return i2c_bus_.Write(i2c_addr_, tx_buffer);
+  }
+
+  // Write the value to register_id
+  pw::Status WriteRegister(void* register_id, void* value) {
+    const size_t command_size = pw::GetIntSize(command_type);
+    const size_t register_size = pw::GetIntSize(register_type);
+    std::byte write_buffer[command_size + register_size];
+
+    pw::ConvertToEndian(register_id,
+                        ByteSpan(write_buffer, command_size),
+                        command_type,
+                        kEndianness);
+
+    pw::ConvertToEndian(value,
+                        ByteSpan(write_buffer + command_size, register_size),
+                        register_type,
+                        kEndianness);
+
+    return i2c_bus_.Write(
+        i2c_addr_, std::span(write_buffer, command_size + register_size));
+  }
+
+  I2cBus& GetI2cBus() { return i2c_bus_; }
+
+  pw::IntSize command_type;
+  pw::IntSize register_type;
+  pw::ByteOrder kEndianness;
+
+ private:
+  I2cBus& i2c_bus_;
+  I2cAddress i2c_addr_;
+};
+
+// Represents a register modification within a class that supports
+// register reads and writes. Typical sequence of operations on such devices
+// is:
+//   1. Load existing values
+//   2. Modify some bits as defined by the data sheet
+//   3. Write the updated value
+// Usage:
+//    RegisterModification  m(device, 0x1234);
+//    m.Bit<0>(true).SetBits<1,4>(3).Commit();
+//    which is the same as:
+//    m.SetBit(0, true).SetBitmask(0x1e, 3 << 1).Commit();
+// Endianess:
+//    Device read/write register handles endianess conversions. Assume
+//    everything is in native endianess (bit 0 is 0x01, bit 4 is 0x10, bit
+//    6 is 0x40, bit 11 is 0x800 and so on).
+//
+// Or if the device returns a modification:
+//   Status s = device.ModifyRegister(0x1234).SetBits<4,8>(3).Commit();
+//
+// Commit returns ok only if both the Read (within Modify) and Write
+// succeeds. If the read fails, no  write is attempted and Commit() will return
+// the read failure code.
+//
+class RegisterModification {
+ public:
+  // Initialize a RegisterModification, load the current value of register
+  RegisterModification(I2cRegisterDevice& device, void* register_id)
+      : device_(device), register_id_(register_id) {
+    load_status_ = device_.ReadRegister(register_id_, value_);
+  }
+
+  // Apply all bit operations.
+  pw::Status Commit() {
+    if (!load_status_.ok()) {
+      return load_status_;
+    }
+
+    return device_.WriteRegister(register_id_, value_);
+  }
+
+  // Modify a subset of bits within the stored value.
+  // The parameters lowBit/highBit are 0-inded bit indices.
+  // Examples:
+  //   SetBits<0, 3>(foo) translates into "value = (value & ~0x0F) | foo"
+  //   SetBits<2, 3>(foo) translates into "value = (value & ~0x0C) | (foo << 2)"
+  template <int lowBit, int highBit>
+  RegisterModification& SetBits(int value) {
+    PW_CHECK_INT_LE(lowBit, highBit);
+    PW_CHECK_INT_GE(lowBit, 0);
+    PW_CHECK_INT_LT(highBit, 8 * GetIntSize(register_type));
+
+    constexpr int bit_count = highBit - lowBit + 1;
+
+    if (command_type == pw::IntSize::kUint8) {
+      uint8_t& value_casted = *reinterpret_cast<uint8_t*>(value_);
+      uint8_t mask = (1 << bit_count) - 1;
+      PW_CHECK_INT_EQ((value_casted & (~mask)), 0);  // check no overflow
+      value_casted &= ~(mask << lowBit);
+      value_casted |= (value & mask) << lowBit;
+    } else if (command_type == pw::IntSize::kUint16) {
+      uint16_t& value_casted = *reinterpret_cast<uint16_t*>(value_);
+      uint16_t mask = (1 << bit_count) - 1;
+      PW_CHECK_INT_EQ((value_casted & (~mask)), 0);  // check no overflow
+      value_casted &= ~(mask << lowBit);
+      value_casted |= (value & mask) << lowBit;
+    } else if (command_type == pw::IntSize::kUint32) {
+      uint32_t& value_casted = *reinterpret_cast<uint32_t*>(value_);
+      uint32_t mask = (1 << bit_count) - 1;
+      PW_CHECK_INT_EQ((value_casted & (~mask)), 0);  // check no overflow
+      value_casted &= ~(mask << lowBit);
+      value_casted |= (value & mask) << lowBit;
+    } else if (command_type == pw::IntSize::kUint64) {
+      uint64_t& value_casted = *reinterpret_cast<uint64_t*>(value_);
+      uint64_t mask = (1 << bit_count) - 1;
+      PW_CHECK_INT_EQ((value_casted & (~mask)), 0);  // check no overflow
+      value_casted &= ~(mask << lowBit);
+      value_casted |= (value & mask) << lowBit;
+    }
+
+    return *this;
+  }
+
+  // Set register value according to mask. Value must be already shifted to
+  // align with mask
+  RegisterModification& SetBitmask(void* mask, void* set_value) {
+    if (command_type == pw::IntSize::kUint8) {
+      uint8_t& value_casted = *reinterpret_cast<uint8_t*>(value_);
+      uint8_t& mask_casted = *reinterpret_cast<uint8_t*>(mask);
+      uint8_t& set_value_casted = *reinterpret_cast<uint8_t*>(set_value);
+      value_casted &= ~mask_casted;
+      value_casted |= (mask_casted & set_value_casted);
+    } else if (command_type == pw::IntSize::kUint16) {
+      uint16_t& value_casted = *reinterpret_cast<uint16_t*>(value_);
+      uint16_t& mask_casted = *reinterpret_cast<uint16_t*>(mask);
+      uint16_t& set_value_casted = *reinterpret_cast<uint16_t*>(set_value);
+      value_casted &= ~mask_casted;
+      value_casted |= (mask_casted & set_value_casted);
+    } else if (command_type == pw::IntSize::kUint32) {
+      uint32_t& value_casted = *reinterpret_cast<uint32_t*>(value_);
+      uint32_t& mask_casted = *reinterpret_cast<uint32_t*>(mask);
+      uint32_t& set_value_casted = *reinterpret_cast<uint32_t*>(set_value);
+      value_casted &= ~mask_casted;
+      value_casted |= (mask_casted & set_value_casted);
+    } else if (command_type == pw::IntSize::kUint64) {
+      uint64_t& value_casted = *reinterpret_cast<uint64_t*>(value_);
+      uint64_t& mask_casted = *reinterpret_cast<uint64_t*>(mask);
+      uint64_t& set_value_casted = *reinterpret_cast<uint64_t*>(set_value);
+      value_casted &= ~mask_casted;
+      value_casted |= (mask_casted & set_value_casted);
+    }
+
+    return *this;
+  }
+
+  // Set or clear bit based on given value
+  RegisterModification& SetBit(uint8_t bitNumber, bool value) {
+    PW_CHECK_INT_LT(bitNumber, 8 * GetIntSize(register_type));
+    if (command_type == pw::IntSize::kUint8) {
+      uint8_t mask = 1 << bitNumber;
+      uint8_t value_shifted = value ? 1 << bitNumber : 0;
+      return SetBitmask(&mask, &value_shifted);
+    } else if (command_type == pw::IntSize::kUint16) {
+      uint16_t mask = 1 << bitNumber;
+      uint16_t value_shifted = value ? 1 << bitNumber : 0;
+      return SetBitmask(&mask, &value_shifted);
+    } else if (command_type == pw::IntSize::kUint32) {
+      uint32_t mask = 1 << bitNumber;
+      uint32_t value_shifted = value ? 1 << bitNumber : 0;
+      return SetBitmask(&mask, &value_shifted);
+    } else if (command_type == pw::IntSize::kUint64) {
+      uint64_t mask = 1 << bitNumber;
+      uint64_t value_shifted = value ? 1 << bitNumber : 0;
+      return SetBitmask(&mask, &value_shifted);
+    }
+  }
+
+  // Set or clear bit based on given value
+  template <int bitNumber>
+  RegisterModification& Bit(bool value) {
+    return SetBit(bitNumber, value);
+  }
+
+  pw::IntSize command_type;
+  pw::IntSize register_type;
+
+ private:
+  I2cRegisterDevice& device_;
+  void* register_id_;
+  void* value_;
+  pw::Status load_status_;
+};
+
+}  // namespace pw::i2c
diff --git a/pw_i2c_baremetal_stm32f429/BUILD b/pw_i2c_baremetal_stm32f429/BUILD
index cd65cb8..c053d22 100644
--- a/pw_i2c_baremetal_stm32f429/BUILD
+++ b/pw_i2c_baremetal_stm32f429/BUILD
@@ -25,7 +25,7 @@
     name = "pw_i2c_baremetal_stm32f429",
     srcs = [
         "i2c_baremetal.cc",
-        "demo_app_main.cc"
+        "demo_app_main.cc",
     ],
     hdrs = [
         "public/pw_i2c_baremetal_stm32f429/i2c_baremetal.h",
diff --git a/pw_i2c_baremetal_stm32f429/i2c_baremetal.cc b/pw_i2c_baremetal_stm32f429/i2c_baremetal.cc
index 02b91e7..6eb1d1c 100644
--- a/pw_i2c_baremetal_stm32f429/i2c_baremetal.cc
+++ b/pw_i2c_baremetal_stm32f429/i2c_baremetal.cc
@@ -13,6 +13,7 @@
 // the License.
 
 #include "pw_i2c_baremetal_stm32f429/i2c_baremetal.h"
+
 #include "pw_preprocessor/compiler.h"
 #include "pw_preprocessor/util.h"
 
diff --git a/targets/stm32f429i-disc1/target_toolchains.gni b/targets/stm32f429i-disc1/target_toolchains.gni
index 241906a..739f042 100644
--- a/targets/stm32f429i-disc1/target_toolchains.gni
+++ b/targets/stm32f429i-disc1/target_toolchains.gni
@@ -64,6 +64,8 @@
     "PW_BOOT_VECTOR_TABLE_BEGIN=0x08000000",
     "PW_BOOT_VECTOR_TABLE_SIZE=512",
   ]
+
+  pw_TARGET_APPLICATIONS = [ "//pw_i2c_baremetal_stm32f429:demo_app" ]
 }
 
 _toolchain_properties = {