pw_i2c: Add RegisterDevice class

The RegisterDevice class contains helpers for reading and writing
register data to and from the device. The helpers allow the user to
easily read and write 1 register worth of data or a set of registers.

Testing:
Host test -- OK

Change-Id: Idaa9db764654103f2a068f3facaa90f96aea95df
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/41164
Commit-Queue: Kevin Zeng <zengk@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_i2c/BUILD b/pw_i2c/BUILD
index 910bff2..7ce2297 100644
--- a/pw_i2c/BUILD
+++ b/pw_i2c/BUILD
@@ -64,6 +64,24 @@
     ],
 )
 
+pw_cc_library(
+    name = "register_device",
+    hdrs = [
+        "public/pw_i2c/register_device.h",
+    ],
+    includes = ["public"],
+    srcs = [ "register_device.cc" ],
+    deps = [
+        ":address",
+        ":device",
+        ":initiator",
+        "//pw_bytes",
+        "//pw_chrono:system_clock",
+        "//pw_result",
+        "//pw_status",
+    ],
+)
+
 pw_cc_test(
     name = "address_test",
     srcs = [
@@ -85,3 +103,15 @@
         "//pw_unit_test",
     ],
 )
+
+pw_cc_test(
+    name = "register_device_test",
+    srcs = [
+        "register_device_test.cc",
+    ],
+    deps = [
+        ":register_device",
+        "//pw_assert",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_i2c/BUILD.gn b/pw_i2c/BUILD.gn
index ed64c41..81fa6c8 100644
--- a/pw_i2c/BUILD.gn
+++ b/pw_i2c/BUILD.gn
@@ -52,10 +52,28 @@
     "$dir_pw_status",
   ]
 }
+
+pw_source_set("register_device") {
+  public_configs = [ ":public_include_path" ]
+  public = [ "public/pw_i2c/register_device.h" ]
+  public_deps = [
+    ":address",
+    ":device",
+    ":initiator",
+    "$dir_pw_bytes",
+    "$dir_pw_chrono:system_clock",
+    "$dir_pw_result",
+    "$dir_pw_status",
+  ]
+  sources = [ "register_device.cc" ]
+  deps = [ "$dir_pw_assert" ]
+}
+
 pw_test_group("tests") {
   tests = [
     ":address_test",
     ":device_test",
+    ":register_device_test",
   ]
 }
 
@@ -70,6 +88,15 @@
   deps = [ ":device" ]
 }
 
+pw_test("register_device_test") {
+  enable_if = pw_chrono_SYSTEM_CLOCK_BACKEND != ""
+  sources = [ "register_device_test.cc" ]
+  deps = [
+    ":register_device",
+    "$dir_pw_assert",
+  ]
+}
+
 pw_doc_group("docs") {
   sources = [ "docs.rst" ]
 }
diff --git a/pw_i2c/docs.rst b/pw_i2c/docs.rst
index 5cb2592..976820e 100644
--- a/pw_i2c/docs.rst
+++ b/pw_i2c/docs.rst
@@ -25,3 +25,11 @@
 contains ``pw::i2c::Address`` and wraps the ``pw::i2c::Initiator`` API.
 Common use case includes streaming arbitrary data (Read/Write). Only works
 with devices with a single device address.
+
+pw::i2c::RegisterDevice
+-----------------------
+The common interface for interfacing with register devices. Contains methods
+to help read and write registers from and to the device. Users should have a
+understanding of the capabilities of their device such as register address
+sizes, register data sizes, byte addressability, bulk transactions, etc in
+order to effectively use this interface.
diff --git a/pw_i2c/public/pw_i2c/register_device.h b/pw_i2c/public/pw_i2c/register_device.h
new file mode 100644
index 0000000..2cc11b1
--- /dev/null
+++ b/pw_i2c/public/pw_i2c/register_device.h
@@ -0,0 +1,404 @@
+// Copyright 2021 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 "pw_bytes/endian.h"
+#include "pw_bytes/span.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_i2c/address.h"
+#include "pw_i2c/device.h"
+#include "pw_i2c/initiator.h"
+#include "pw_result/result.h"
+#include "pw_status/status.h"
+#include "pw_status/try.h"
+
+namespace pw {
+namespace i2c {
+
+enum class RegisterAddressSize {
+  k1Byte = 1,
+  k2Bytes = 2,
+  k4Bytes = 4,
+};
+
+// RegisterDevice is used to write/read registers, chunks of data
+// or just an array of bytes over a bus to a device.
+//
+// DISCLAIMER:
+// It is important to note that bulk write/read may not be supported for every
+// device and that it's up to the user to know the capabilities of their device.
+// Users should also be aware of the register and address size and use the
+// appropriate methods for their device.
+//
+//  - WriteRegisters*
+//       Write to a set of registers starting at a specific address/offset.
+//       Endianness will be applied to data that's read or written.
+//
+//  - WriteRegister*
+//       Write data to a register where the max register size is 4 bytes.
+//       Endianness will be applied to data that's read or written.
+//
+//  - ReadRegisters*
+//       Read a set of registers starting at a specific address/offset.
+//       Endianness will be applied to data that's read or written.
+//
+//  - ReadRegister*
+//       Read data to a register where the max register size is 4 bytes.
+//       Endianness will be applied to data that's read or written.
+class RegisterDevice : public Device {
+ public:
+  // Args:
+  //   initiator: I2C initiator for the bus the device is on.
+  //   address: I2C device address.
+  //   order: Endianness of the register address and data.
+  //   register_address_size: Size of the register address.
+  constexpr RegisterDevice(Initiator& initiator,
+                           Address address,
+                           std::endian order,
+                           RegisterAddressSize register_address_size)
+      : Device(initiator, address),
+        order_(order),
+        register_address_size_(register_address_size) {}
+
+  // Writes data to multiple contiguous registers starting at specific register.
+  // WriteRegisters has byte addressable capabilities and it is up to the user
+  // to determine the appropriate size based on the features of the device. The
+  // amount of data to write is the size of the span. Endianness is taken into
+  // account if register_data_size is 2 bytes or 4 bytes. Both address and
+  // data will use the same endianness provided by the constructor.
+  //
+  // It is important to note that bulk write may not be supported for every
+  // device and that it's up to the user to know the capabilities of their
+  // device. Args:
+  //   register_address: Register address to send.
+  //   register_data: Data to write.
+  //   buffer: Since we need a buffer to construct the write data that consists
+  //           of the register address and the register data, the buffer should
+  //           be big enough such that the two can be concatenated.
+  //   for_at_least: timeout that's used for both lock and transaction (ms).
+  // Returns:
+  //   Ok: Successful.
+  //   DeadlineExceeded: Unable to acquire exclusive Initiator access and
+  //                     complete the I2C transaction in time.
+  //   FailedPrecondition: Interface is not initialized and/or enabled.
+  //   Internal: Building data for the write buffer has an issue.
+  //   InvalidArgument: Device_address is larger than the 10 bit address space.
+  //   OutOfRange: if buffer size is too small for data and register_address.
+  //   Unavailable: if NACK and device did not respond in time.
+  Status WriteRegisters(uint32_t register_address,
+                        ConstByteSpan register_data,
+                        ByteSpan buffer,
+                        chrono::SystemClock::duration for_at_least);
+
+  Status WriteRegisters8(uint32_t register_address,
+                         std::span<const uint8_t> register_data,
+                         ByteSpan buffer,
+                         chrono::SystemClock::duration for_at_least);
+
+  Status WriteRegisters16(uint32_t register_address,
+                          std::span<const uint16_t> register_data,
+                          ByteSpan buffer,
+                          chrono::SystemClock::duration for_at_least);
+
+  Status WriteRegisters32(uint32_t register_address,
+                          std::span<const uint32_t> register_data,
+                          ByteSpan buffer,
+                          chrono::SystemClock::duration for_at_least);
+
+  // Reads data chunk starting at specific offset or register.
+  // ReadRegisters has byte addressable capabilities and it is up to the user
+  // to determine the appropriate size based on the features of the device. The
+  // amount of data to read is the size of the span. Endianness is taken into
+  // account for the *16 and *32 bit methods.  Both address and data will use
+  // the same endianness provided by the constructor.
+  //
+  // It is important to note that bulk read may not be supported for every
+  // device and that it's up to the user to know the capabilities of their
+  // device. Args:
+  //   register_address: Register address to send.
+  //   return_data: Area to read data to.
+  //   for_at_least: Timeout that's used for both lock and transaction (ms).
+  // Returns:
+  //   Ok: Successful.
+  //   DeadlineExceeded: Unable to acquire exclusive Initiator access and
+  //                     complete the I2C transaction in time.
+  //   FailedPrecondition: Interface is not initialized and/or enabled.
+  //   Internal: Building data for the write buffer has an issue.
+  //   InvalidArgument: Device_address is larger than the 10 bit address space.
+  //   Unavailable: if NACK and device did not respond in time.
+  Status ReadRegisters(uint32_t register_address,
+                       ByteSpan return_data,
+                       chrono::SystemClock::duration for_at_least);
+
+  Status ReadRegisters8(uint32_t register_address,
+                        std::span<uint8_t> return_data,
+                        chrono::SystemClock::duration for_at_least);
+
+  Status ReadRegisters16(uint32_t register_address,
+                         std::span<uint16_t> return_data,
+                         chrono::SystemClock::duration for_at_least);
+
+  Status ReadRegisters32(uint32_t register_address,
+                         std::span<uint32_t> return_data,
+                         chrono::SystemClock::duration for_at_least);
+
+  // Writes the register address first before data.
+  // User should be careful which WriteRegister* API is used and should use
+  // the one that matches their register data size if not byte addressable.
+  //
+  // Both address and data will use the same endianness provided by the
+  // constructor.
+  // Args:
+  //   register_address: Register address to send.
+  //   register_data: Data to write.
+  //   for_at_least: Timeout that's used for both lock and transaction (ms).
+  // Returns:
+  //   Ok: Successful.
+  //   DeadlineExceeded: Unable to acquire exclusive Initiator access and
+  //                     complete the I2C transaction in time.
+  //   FailedPrecondition: Interface is not initialized and/or enabled.
+  //   Internal: Building data for the write buffer has an issue.
+  //   InvalidArgument: Device_address is larger than the 10 bit address space.
+  //   Unavailable: if NACK and device did not respond in time.
+  Status WriteRegister(uint32_t register_address,
+                       std::byte register_data,
+                       chrono::SystemClock::duration for_at_least);
+
+  Status WriteRegister8(uint32_t register_address,
+                        uint8_t register_data,
+                        chrono::SystemClock::duration for_at_least);
+
+  Status WriteRegister16(uint32_t register_address,
+                         uint16_t register_data,
+                         chrono::SystemClock::duration for_at_least);
+
+  Status WriteRegister32(uint32_t register_address,
+                         uint32_t register_data,
+                         chrono::SystemClock::duration for_at_least);
+
+  // Reads data from the device after sending the register address first.
+  // User should be careful which ReadRegister* API is used and should use
+  // the one that matches their register data size if not byte addressable.
+  //
+  // Both address and data will use the same endianness provided by the
+  // constructor.
+  // Args:
+  //   register_address: Register address to send.
+  //   for_at_least: Timeout that's used for both lock and transaction (ms).
+  // Returns:
+  //   Ok: Successful.
+  //   DeadlineExceeded: Unable to acquire exclusive Initiator access and
+  //                     complete the I2C transaction in time.
+  //   FailedPrecondition: Interface is not initialized and/or enabled.
+  //   Internal: Building data for the write buffer has an issue.
+  //   InvalidArgument: Device_address is larger than the 10 bit address space.
+  //   Unavailable: if NACK and device did not respond in time.
+  Result<std::byte> ReadRegister(uint32_t register_address,
+                                 chrono::SystemClock::duration for_at_least);
+
+  Result<uint8_t> ReadRegister8(uint32_t register_address,
+                                chrono::SystemClock::duration for_at_least);
+
+  Result<uint16_t> ReadRegister16(uint32_t register_address,
+                                  chrono::SystemClock::duration for_at_least);
+
+  Result<uint32_t> ReadRegister32(uint32_t register_address,
+                                  chrono::SystemClock::duration for_at_least);
+
+ private:
+  // Helper write registers.
+  Status WriteRegisters(uint32_t register_address,
+                        ConstByteSpan register_data,
+                        const size_t register_data_size,
+                        ByteSpan buffer,
+                        chrono::SystemClock::duration for_at_least);
+
+  const std::endian order_;
+  const RegisterAddressSize register_address_size_;
+};
+
+inline Status RegisterDevice::WriteRegisters(
+    uint32_t register_address,
+    ConstByteSpan register_data,
+    ByteSpan buffer,
+    chrono::SystemClock::duration for_at_least) {
+  return WriteRegisters(register_address,
+                        register_data,
+                        sizeof(decltype(register_data)::value_type),
+                        buffer,
+                        for_at_least);
+}
+
+inline Status RegisterDevice::WriteRegisters8(
+    uint32_t register_address,
+    std::span<const uint8_t> register_data,
+    ByteSpan buffer,
+    chrono::SystemClock::duration for_at_least) {
+  return WriteRegisters(register_address,
+                        std::as_bytes(register_data),
+                        sizeof(decltype(register_data)::value_type),
+                        buffer,
+                        for_at_least);
+}
+
+inline Status RegisterDevice::WriteRegisters16(
+    uint32_t register_address,
+    std::span<const uint16_t> register_data,
+    ByteSpan buffer,
+    chrono::SystemClock::duration for_at_least) {
+  return WriteRegisters(register_address,
+                        std::as_bytes(register_data),
+                        sizeof(decltype(register_data)::value_type),
+                        buffer,
+                        for_at_least);
+}
+
+inline Status RegisterDevice::WriteRegisters32(
+    uint32_t register_address,
+    std::span<const uint32_t> register_data,
+    ByteSpan buffer,
+    chrono::SystemClock::duration for_at_least) {
+  return WriteRegisters(register_address,
+                        std::as_bytes(register_data),
+                        sizeof(decltype(register_data)::value_type),
+                        buffer,
+                        for_at_least);
+}
+
+inline Status RegisterDevice::WriteRegister(
+    uint32_t register_address,
+    std::byte register_data,
+    chrono::SystemClock::duration for_at_least) {
+  std::array<std::byte, sizeof(register_data) + sizeof(register_address)>
+      byte_buffer;
+  return WriteRegisters(register_address,
+                        std::span(&register_data, 1),
+                        sizeof(register_data),
+                        byte_buffer,
+                        for_at_least);
+}
+
+inline Status RegisterDevice::WriteRegister8(
+    uint32_t register_address,
+    uint8_t register_data,
+    chrono::SystemClock::duration for_at_least) {
+  std::array<std::byte, sizeof(register_data) + sizeof(register_address)>
+      byte_buffer;
+  return WriteRegisters(register_address,
+                        std::as_bytes(std::span(&register_data, 1)),
+                        sizeof(register_data),
+                        byte_buffer,
+                        for_at_least);
+}
+
+inline Status RegisterDevice::WriteRegister16(
+    uint32_t register_address,
+    uint16_t register_data,
+    chrono::SystemClock::duration for_at_least) {
+  std::array<std::byte, sizeof(register_data) + sizeof(register_address)>
+      byte_buffer;
+  return WriteRegisters(register_address,
+                        std::as_bytes(std::span(&register_data, 1)),
+                        sizeof(register_data),
+                        byte_buffer,
+                        for_at_least);
+}
+
+inline Status RegisterDevice::WriteRegister32(
+    uint32_t register_address,
+    uint32_t register_data,
+    chrono::SystemClock::duration for_at_least) {
+  std::array<std::byte, sizeof(register_data) + sizeof(register_address)>
+      byte_buffer;
+  return WriteRegisters(register_address,
+                        std::as_bytes(std::span(&register_data, 1)),
+                        sizeof(register_data),
+                        byte_buffer,
+                        for_at_least);
+}
+
+inline Status RegisterDevice::ReadRegisters8(
+    uint32_t register_address,
+    std::span<uint8_t> return_data,
+    chrono::SystemClock::duration for_at_least) {
+  // For a single byte, there's no endian data, and so we can return the
+  // data as is.
+  return ReadRegisters(
+      register_address, std::as_writable_bytes(return_data), for_at_least);
+}
+
+inline Status RegisterDevice::ReadRegisters16(
+    uint32_t register_address,
+    std::span<uint16_t> return_data,
+    chrono::SystemClock::duration for_at_least) {
+  PW_TRY(ReadRegisters(
+      register_address, std::as_writable_bytes(return_data), for_at_least));
+
+  // Post process endian information.
+  for (uint16_t& register_value : return_data) {
+    register_value = bytes::ReadInOrder<uint16_t>(order_, &register_value);
+  }
+
+  return pw::OkStatus();
+}
+
+inline Status RegisterDevice::ReadRegisters32(
+    uint32_t register_address,
+    std::span<uint32_t> return_data,
+    chrono::SystemClock::duration for_at_least) {
+  PW_TRY(ReadRegisters(
+      register_address, std::as_writable_bytes(return_data), for_at_least));
+
+  // TODO(b/185952662): Extend endian in pw_byte to support this conversion
+  //                    as optimization.
+  // Post process endian information.
+  for (uint32_t& register_value : return_data) {
+    register_value = bytes::ReadInOrder<uint32_t>(order_, &register_value);
+  }
+
+  return pw::OkStatus();
+}
+
+inline Result<std::byte> RegisterDevice::ReadRegister(
+    uint32_t register_address, chrono::SystemClock::duration for_at_least) {
+  std::byte data = {};
+  PW_TRY(ReadRegisters(register_address, std::span(&data, 1), for_at_least));
+  return data;
+}
+
+inline Result<uint8_t> RegisterDevice::ReadRegister8(
+    uint32_t register_address, chrono::SystemClock::duration for_at_least) {
+  uint8_t data = 0;
+  PW_TRY(ReadRegisters8(register_address, std::span(&data, 1), for_at_least));
+  return data;
+}
+
+inline Result<uint16_t> RegisterDevice::ReadRegister16(
+    uint32_t register_address, chrono::SystemClock::duration for_at_least) {
+  std::array<uint16_t, 1> data = {};
+  PW_TRY(ReadRegisters16(register_address, data, for_at_least));
+  return data[0];
+}
+
+inline Result<uint32_t> RegisterDevice::ReadRegister32(
+    uint32_t register_address, chrono::SystemClock::duration for_at_least) {
+  std::array<uint32_t, 1> data = {};
+  PW_TRY(ReadRegisters32(register_address, data, for_at_least));
+  return data[0];
+}
+
+}  // namespace i2c
+}  // namespace pw
+
+// TODO (zengk): Register modification.
diff --git a/pw_i2c/register_device.cc b/pw_i2c/register_device.cc
new file mode 100644
index 0000000..af8183a
--- /dev/null
+++ b/pw_i2c/register_device.cc
@@ -0,0 +1,148 @@
+// Copyright 2021 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.
+
+#include "pw_i2c/register_device.h"
+
+#include "pw_bytes/byte_builder.h"
+
+namespace pw {
+namespace i2c {
+namespace {
+
+// Puts the register address data into the buffer based on the size of the
+// register address.
+void PutRegisterAddressInByteBuilder(
+    ByteBuilder& byte_builder,
+    const uint32_t register_address,
+    const std::endian order,
+    RegisterAddressSize register_address_size) {
+  // TODO(b/185952662): Simplify the call site by extending the byte builder
+  //                   and endian API.
+  switch (register_address_size) {
+    case RegisterAddressSize::k1Byte:
+      byte_builder.PutUint8(static_cast<uint8_t>(register_address));
+      break;
+
+    case RegisterAddressSize::k2Bytes:
+      byte_builder.PutUint16(static_cast<uint16_t>(register_address), order);
+      break;
+
+    case RegisterAddressSize::k4Bytes:
+      byte_builder.PutUint32(register_address, order);
+      break;
+
+    default:
+      PW_CRASH("Invalid address size being put in byte buffer");
+  }
+}
+
+void PutRegisterData16InByteBuilder(ByteBuilder& byte_builder,
+                                    ConstByteSpan register_data,
+                                    const std::endian order) {
+  uint32_t data_pointer_index = 0;
+
+  while (data_pointer_index < register_data.size()) {
+    const uint16_t data = *reinterpret_cast<const uint16_t*>(
+        register_data.data() + data_pointer_index);
+    byte_builder.PutUint16(data, order);
+    data_pointer_index += sizeof(data);
+  }
+}
+
+Status PutRegisterData32InByteBuilder(ByteBuilder& byte_builder,
+                                      ConstByteSpan register_data,
+                                      const std::endian order) {
+  uint32_t data_pointer_index = 0;
+
+  while (data_pointer_index < register_data.size()) {
+    const uint32_t data = *reinterpret_cast<const uint32_t*>(
+        register_data.data() + data_pointer_index);
+    byte_builder.PutUint32(data, order);
+    data_pointer_index += sizeof(data);
+  }
+
+  if (data_pointer_index == register_data.size()) {
+    return pw::OkStatus();
+  } else {
+    // The write data that was given doesn't align with the expected register
+    // data size.
+    return Status::InvalidArgument();
+  }
+}
+
+}  // namespace
+
+Status RegisterDevice::WriteRegisters(
+    const uint32_t register_address,
+    ConstByteSpan register_data,
+    const size_t register_data_size,
+    ByteSpan buffer,
+    chrono::SystemClock::duration for_at_least) {
+  // Make sure the buffer is big enough to handle the address and data.
+  if (buffer.size() <
+      register_data.size() + static_cast<uint32_t>(register_address_size_)) {
+    return pw::Status::OutOfRange();
+  }
+
+  ByteBuilder builder = ByteBuilder(buffer);
+  PutRegisterAddressInByteBuilder(
+      builder, register_address, order_, register_address_size_);
+
+  switch (register_data_size) {
+    case 1:
+      builder.append(register_data.data(), register_data.size());
+      break;
+
+    case 2:
+      PutRegisterData16InByteBuilder(builder, register_data, order_);
+      break;
+
+    case 4:
+      PutRegisterData32InByteBuilder(builder, register_data, order_);
+      break;
+
+    default:
+      PW_CRASH("Invalid data size being put in byte buffer");
+  }
+
+  if (!builder.ok()) {
+    return pw::Status::Internal();
+  }
+
+  ConstByteSpan write_buffer(builder.data(), builder.size());
+  return WriteFor(write_buffer, for_at_least);
+}
+
+Status RegisterDevice::ReadRegisters(
+    uint32_t register_address,
+    ByteSpan return_data,
+    chrono::SystemClock::duration for_at_least) {
+  ByteBuffer<sizeof(register_address)> byte_buffer;
+
+  PutRegisterAddressInByteBuilder(
+      byte_buffer, register_address, order_, register_address_size_);
+
+  if (!byte_buffer.ok()) {
+    return pw::Status::Internal();
+  }
+
+  return WriteReadFor(byte_buffer.data(),
+                      byte_buffer.size(),
+                      return_data.data(),
+                      return_data.size(),
+                      for_at_least);
+}
+
+}  // namespace i2c
+}  // namespace pw
diff --git a/pw_i2c/register_device_test.cc b/pw_i2c/register_device_test.cc
new file mode 100644
index 0000000..ef83eed
--- /dev/null
+++ b/pw_i2c/register_device_test.cc
@@ -0,0 +1,756 @@
+// Copyright 2021 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.
+#include "pw_i2c/register_device.h"
+
+#include "gtest/gtest.h"
+#include "pw_assert/assert.h"
+#include "pw_bytes/byte_builder.h"
+
+namespace pw {
+namespace i2c {
+namespace {
+
+using ::pw::Status;
+using namespace std::literals::chrono_literals;
+
+constexpr uint8_t kErrorValue = 0x11;
+constexpr Address kDummyDeviceAddress = Address::SevenBit<0x3F>();
+
+constexpr chrono::SystemClock::duration kTimeout =
+    std::chrono::duration_cast<chrono::SystemClock::duration>(100ms);
+
+// Default test object. Mimics closely to I2c devices.
+class TestInitiator : public Initiator {
+ public:
+  explicit TestInitiator() {}
+
+  ByteBuilder& GetWriteBuffer() { return write_buffer_; }
+
+  void SetReadData(ByteSpan read_data) {
+    read_buffer_.append(read_data.data(), read_data.size());
+  }
+
+ private:
+  Status DoWriteReadFor(Address,
+                        ConstByteSpan tx_data,
+                        ByteSpan rx_data,
+                        chrono::SystemClock::duration) override {
+    // Write
+    if (tx_data.size() > 0) {
+      write_buffer_.append(tx_data.data(), tx_data.size());
+    }
+
+    // Read
+    if (rx_data.size() > 0) {
+      PW_CHECK_UINT_EQ(
+          read_buffer_.size(), rx_data.size(), "Buffer to read is too big");
+      for (uint32_t i = 0; i < rx_data.size(); i++) {
+        rx_data[i] = read_buffer_.data()[i];
+      }
+    }
+
+    return OkStatus();
+  }
+
+  ByteBuffer<10> write_buffer_;
+  ByteBuffer<10> read_buffer_;
+};
+
+TEST(RegisterDevice, Construction) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k1Byte);
+}
+
+TEST(RegisterDevice, WriteRegisters8With2RegistersAnd1ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k1Byte);
+
+  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
+  std::array<std::byte, 3> builder;
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  EXPECT_EQ(
+      device.WriteRegisters(kRegisterAddress, register_data, builder, kTimeout),
+      pw::OkStatus());
+
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(sizeof(builder), test_device_builder.size());
+
+  // Check address.
+  EXPECT_EQ(kRegisterAddress,
+            static_cast<uint32_t>(test_device_builder.data()[0]));
+
+  // Check data.
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
+  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
+    EXPECT_EQ(register_data[i], test_device_builder.data()[i + kAddressSize]);
+  }
+}
+
+TEST(RegisterDevice, WriteRegisters8With2RegistersAnd2ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k2Bytes);
+
+  constexpr uint32_t kRegisterAddress = 0x89AB;
+  std::byte register_data[2] = {std::byte{0xCD}, std::byte{0xEF}};
+  std::array<std::byte, 4> builder;
+  EXPECT_EQ(
+      device.WriteRegisters(kRegisterAddress, register_data, builder, kTimeout),
+      pw::OkStatus());
+
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(sizeof(builder), test_device_builder.size());
+
+  // Check address.
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(test_device_builder.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
+  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
+    EXPECT_EQ(register_data[i], test_device_builder.data()[i + kAddressSize]);
+  }
+}
+
+TEST(RegisterDevice, WriteRegisters16With2RegistersAnd2ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k2Bytes);
+
+  constexpr uint32_t kRegisterAddress = 0x89AB;
+  std::array<uint16_t, 2> register_data = {0xCDEF, 0x1234};
+  std::array<std::byte, 6> builder;
+  EXPECT_EQ(device.WriteRegisters16(
+                kRegisterAddress, register_data, builder, kTimeout),
+            pw::OkStatus());
+
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(sizeof(builder), test_device_builder.size());
+
+  // Check address.
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(test_device_builder.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
+
+  const uint16_t* read_pointer = reinterpret_cast<const uint16_t*>(
+      test_device_builder.data() + kAddressSize);
+  for (uint32_t i = 0; i < (test_device_builder.size() - kAddressSize) /
+                               sizeof(register_data[0]);
+       i++) {
+    EXPECT_EQ(register_data[i], read_pointer[i]);
+  }
+}
+
+TEST(RegisterDevice, WriteRegisters16With2RegistersAnd2ByteAddressBigEndian) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::big,
+                        RegisterAddressSize::k2Bytes);
+
+  constexpr uint32_t kRegisterAddress = 0x89AB;
+  std::array<uint16_t, 2> register_data = {0xCDEF, 0x1234};
+  std::array<std::byte, 6> builder;
+  EXPECT_EQ(device.WriteRegisters16(
+                kRegisterAddress, register_data, builder, kTimeout),
+            pw::OkStatus());
+
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(sizeof(builder), test_device_builder.size());
+
+  // Check address.
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(test_device_builder.data())));
+  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
+            kActualAddress);
+
+  // Check data.
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
+
+  const uint16_t* read_pointer = reinterpret_cast<const uint16_t*>(
+      test_device_builder.data() + kAddressSize);
+  for (uint32_t i = 0; i < (test_device_builder.size() - kAddressSize) /
+                               sizeof(register_data[0]);
+       i++) {
+    EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &register_data[i]),
+              read_pointer[i]);
+  }
+}
+
+TEST(RegisterDevice, WriteRegisters8BufferTooSmall) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k2Bytes);
+
+  constexpr uint32_t kRegisterAddress = 0x89AB;
+  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
+  std::array<std::byte, 2> builder;
+  EXPECT_EQ(
+      device.WriteRegisters(kRegisterAddress, register_data, builder, kTimeout),
+      pw::Status::OutOfRange());
+}
+
+TEST(RegisterDevice, WriteRegister16With1ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k1Byte);
+
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  constexpr uint16_t kRegisterData = 0xBCDE;
+  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
+            pw::OkStatus());
+
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
+
+  // Check address.
+  EXPECT_EQ(kRegisterAddress,
+            static_cast<uint32_t>(test_device_builder.data()[0]));
+
+  // Check data.
+  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
+    EXPECT_EQ(
+        (kRegisterData >> (8 * i)) & 0xFF,
+        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
+  }
+}
+
+TEST(RegisterDevice, WriteRegister32With1ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k1Byte);
+
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  constexpr uint32_t kRegisterData = 0xBCCDDEEF;
+  EXPECT_EQ(device.WriteRegister32(kRegisterAddress, kRegisterData, kTimeout),
+            pw::OkStatus());
+
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
+
+  // Check address.
+  EXPECT_EQ(kRegisterAddress,
+            static_cast<uint32_t>(test_device_builder.data()[0]));
+
+  // Check data.
+  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
+    EXPECT_EQ(
+        (kRegisterData >> (8 * i)) & 0xFF,
+        static_cast<uint32_t>(test_device_builder.data()[i + kAddressSize]));
+  }
+}
+
+TEST(RegisterDevice, WriteRegister16with2ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k2Bytes);
+
+  constexpr uint32_t kRegisterAddress = 0xAB23;
+  constexpr uint16_t kRegisterData = 0xBCDD;
+  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
+            pw::OkStatus());
+
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
+
+  // Check address.
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(test_device_builder.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
+    EXPECT_EQ(
+        (kRegisterData >> (8 * i)) & 0xFF,
+        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
+  }
+}
+
+TEST(RegisterDevice, WriteRegister16With1ByteAddressAndBigEndian) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::big,
+                        RegisterAddressSize::k1Byte);
+
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  constexpr uint16_t kRegisterData = 0xBCDE;
+  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
+            pw::OkStatus());
+
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
+
+  // Check address.
+  EXPECT_EQ(kRegisterAddress,
+            static_cast<uint32_t>(test_device_builder.data()[0]));
+
+  // Check data.
+  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
+    const uint32_t shift = test_device_builder.size() - kAddressSize - (i + 1);
+    EXPECT_EQ(
+        (kRegisterData >> (8 * shift)) & 0xFF,
+        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
+  }
+}
+
+TEST(RegisterDevice, WriteRegister32With1ByteAddressAndBigEndian) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::big,
+                        RegisterAddressSize::k1Byte);
+
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  constexpr uint32_t kRegisterData = 0xBCCDDEEF;
+  EXPECT_EQ(device.WriteRegister32(kRegisterAddress, kRegisterData, kTimeout),
+            pw::OkStatus());
+
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k1Byte);
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
+
+  // Check address.
+  EXPECT_EQ(kRegisterAddress,
+            static_cast<uint32_t>(test_device_builder.data()[0]));
+
+  // Check data.
+  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
+    const uint32_t shift = test_device_builder.size() - kAddressSize - (i + 1);
+    EXPECT_EQ(
+        (kRegisterData >> (8 * shift)) & 0xFF,
+        static_cast<uint32_t>(test_device_builder.data()[i + kAddressSize]));
+  }
+}
+
+TEST(RegisterDevice, WriteRegister16With2ByteAddressAndBigEndian) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::big,
+                        RegisterAddressSize::k2Bytes);
+
+  constexpr uint32_t kRegisterAddress = 0xAB11;
+  constexpr uint16_t kRegisterData = 0xBCDF;
+  EXPECT_EQ(device.WriteRegister16(kRegisterAddress, kRegisterData, kTimeout),
+            pw::OkStatus());
+
+  constexpr uint32_t kAddressSize =
+      static_cast<uint32_t>(RegisterAddressSize::k2Bytes);
+  ByteBuilder& test_device_builder = initiator.GetWriteBuffer();
+  EXPECT_EQ(test_device_builder.size(), kAddressSize + sizeof(kRegisterData));
+
+  // Check address.
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(test_device_builder.data())));
+  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
+            kActualAddress);
+
+  // Check data.
+  for (uint32_t i = 0; i < test_device_builder.size() - kAddressSize; i++) {
+    const uint32_t shift = test_device_builder.size() - kAddressSize - (i + 1);
+    EXPECT_EQ(
+        (kRegisterData >> (8 * shift)) & 0xFF,
+        static_cast<uint16_t>(test_device_builder.data()[i + kAddressSize]));
+  }
+}
+
+TEST(RegisterDevice, ReadRegisters8ByteWith2RegistersAnd1ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k1Byte);
+
+  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
+  initiator.SetReadData(register_data);
+
+  std::array<std::byte, 2> buffer;
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  EXPECT_EQ(device.ReadRegisters(kRegisterAddress, buffer, kTimeout),
+            pw::OkStatus());
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
+            address_buffer.size());
+
+  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  for (uint32_t i = 0; i < sizeof(buffer); i++) {
+    EXPECT_EQ(buffer[i], register_data[i]);
+  }
+}
+
+TEST(RegisterDevice, ReadRegisters8IntWith2RegistersAnd1ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k1Byte);
+
+  std::array<uint8_t, 2> register_data = {0xCD, 0xEF};
+  initiator.SetReadData(std::as_writable_bytes(
+      std::span(register_data.data(), register_data.size())));
+
+  std::array<uint8_t, 2> buffer;
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  EXPECT_EQ(device.ReadRegisters8(kRegisterAddress, buffer, kTimeout),
+            pw::OkStatus());
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
+            address_buffer.size());
+
+  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  for (uint32_t i = 0; i < sizeof(buffer); i++) {
+    EXPECT_EQ(buffer[i], register_data[i]);
+  }
+}
+
+TEST(RegisterDevice, ReadRegisters8ByteWith2RegistersAnd2ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k2Bytes);
+
+  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
+  initiator.SetReadData(register_data);
+
+  std::array<std::byte, 2> buffer;
+  constexpr uint32_t kRegisterAddress = 0xABBA;
+  EXPECT_EQ(device.ReadRegisters(kRegisterAddress, buffer, kTimeout),
+            pw::OkStatus());
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
+            address_buffer.size());
+
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  for (uint32_t i = 0; i < sizeof(buffer); i++) {
+    EXPECT_EQ(buffer[i], register_data[i]);
+  }
+}
+
+TEST(RegisterDevice, ReadRegisters16With2RegistersAnd2ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k2Bytes);
+
+  std::array<uint16_t, 2> register_data = {0xCDEF, 0x1234};
+  initiator.SetReadData(std::as_writable_bytes(
+      std::span(register_data.data(), register_data.size())));
+
+  std::array<uint16_t, 2> buffer;
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  EXPECT_EQ(device.ReadRegisters16(kRegisterAddress, buffer, kTimeout),
+            pw::OkStatus());
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
+            address_buffer.size());
+
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  for (uint32_t i = 0; i < buffer.size(); i++) {
+    EXPECT_EQ(buffer[i], register_data[i]);
+  }
+}
+
+TEST(RegisterDevice, ReadRegisters16With2RegistersAnd2ByteAddressBigEndian) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::big,
+                        RegisterAddressSize::k2Bytes);
+
+  std::array<uint16_t, 2> register_data = {0xCDEF, 0x1234};
+  initiator.SetReadData(std::as_writable_bytes(
+      std::span(register_data.data(), register_data.size())));
+
+  std::array<uint16_t, 2> buffer;
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  EXPECT_EQ(device.ReadRegisters16(kRegisterAddress, buffer, kTimeout),
+            pw::OkStatus());
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
+            address_buffer.size());
+
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
+            kActualAddress);
+
+  // Check data.
+  for (uint32_t i = 0; i < buffer.size(); i++) {
+    EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &register_data[i]),
+              buffer[i]);
+  }
+}
+
+TEST(RegisterDevice, ReadRegister16With1ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k1Byte);
+
+  std::array<std::byte, 2> register_data = {std::byte{0xCD}, std::byte{0xEF}};
+  initiator.SetReadData(register_data);
+
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
+  EXPECT_TRUE(result.ok());
+  uint16_t read_data = result.value_or(kErrorValue);
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
+            address_buffer.size());
+
+  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
+  for (uint32_t i = 0; i < sizeof(read_data); i++) {
+    EXPECT_EQ(read_pointer[i], static_cast<uint8_t>(register_data[i]));
+  }
+}
+
+TEST(RegisterDevice, ReadRegister32With1ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k1Byte);
+
+  std::array<std::byte, 4> register_data = {
+      std::byte{0x98}, std::byte{0x76}, std::byte{0x54}, std::byte{0x32}};
+  initiator.SetReadData(register_data);
+
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  Result<uint32_t> result = device.ReadRegister32(kRegisterAddress, kTimeout);
+  EXPECT_TRUE(result.ok());
+  uint32_t read_data = result.value_or(kErrorValue);
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
+            address_buffer.size());
+
+  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
+  for (uint32_t i = 0; i < sizeof(read_data); i++) {
+    EXPECT_EQ(read_pointer[i], static_cast<uint8_t>(register_data[i]));
+  }
+}
+
+TEST(RegisterDevice, ReadRegister16With2ByteAddress) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::little,
+                        RegisterAddressSize::k2Bytes);
+
+  std::array<std::byte, 2> register_data = {std::byte{0x98}, std::byte{0x76}};
+  initiator.SetReadData(register_data);
+
+  constexpr uint32_t kRegisterAddress = 0xA4AB;
+  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
+  EXPECT_TRUE(result.ok());
+  uint16_t read_data = result.value_or(kErrorValue);
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
+            address_buffer.size());
+
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
+  for (uint32_t i = 0; i < sizeof(read_data); i++) {
+    EXPECT_EQ(read_pointer[i], static_cast<uint8_t>(register_data[i]));
+  }
+}
+
+TEST(RegisterDevice, ReadRegister16With1ByteAddressAndBigEndian) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::big,
+                        RegisterAddressSize::k1Byte);
+
+  std::array<std::byte, 2> register_data = {std::byte{0x98}, std::byte{0x76}};
+  initiator.SetReadData(register_data);
+
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
+  EXPECT_TRUE(result.ok());
+  uint16_t read_data = result.value_or(kErrorValue);
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
+            address_buffer.size());
+
+  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
+  for (uint32_t i = 0; i < sizeof(read_data); i++) {
+    const uint32_t kReadPointerIndex = sizeof(read_data) - 1 - i;
+    EXPECT_EQ(read_pointer[kReadPointerIndex],
+              static_cast<uint8_t>(register_data[i]));
+  }
+}
+
+TEST(RegisterDevice, ReadRegister32With1ByteAddressAndBigEndian) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::big,
+                        RegisterAddressSize::k1Byte);
+
+  std::array<std::byte, 4> register_data = {
+      std::byte{0x98}, std::byte{0x76}, std::byte{0x54}, std::byte{0x32}};
+  initiator.SetReadData(register_data);
+
+  constexpr uint32_t kRegisterAddress = 0xAB;
+  Result<uint32_t> result = device.ReadRegister32(kRegisterAddress, kTimeout);
+  EXPECT_TRUE(result.ok());
+  uint32_t read_data = result.value_or(kErrorValue);
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k1Byte),
+            address_buffer.size());
+
+  const uint8_t kActualAddress = *(reinterpret_cast<uint8_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(kRegisterAddress, kActualAddress);
+
+  // Check data.
+  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
+  for (uint32_t i = 0; i < sizeof(read_data); i++) {
+    const uint32_t kReadPointerIndex = sizeof(read_data) - 1 - i;
+    EXPECT_EQ(read_pointer[kReadPointerIndex],
+              static_cast<uint8_t>(register_data[i]));
+  }
+}
+
+TEST(RegisterDevice, ReadRegister16With2ByteAddressAndBigEndian) {
+  TestInitiator initiator;
+  RegisterDevice device(initiator,
+                        kDummyDeviceAddress,
+                        std::endian::big,
+                        RegisterAddressSize::k2Bytes);
+
+  std::array<std::byte, 2> register_data = {std::byte{0x98}, std::byte{0x76}};
+  initiator.SetReadData(register_data);
+
+  constexpr uint32_t kRegisterAddress = 0xABEF;
+  Result<uint16_t> result = device.ReadRegister16(kRegisterAddress, kTimeout);
+  EXPECT_TRUE(result.ok());
+  uint16_t read_data = result.value_or(kErrorValue);
+
+  // Check address.
+  ByteBuilder& address_buffer = initiator.GetWriteBuffer();
+  EXPECT_EQ(static_cast<uint32_t>(RegisterAddressSize::k2Bytes),
+            address_buffer.size());
+
+  const uint16_t kActualAddress = *(reinterpret_cast<uint16_t*>(
+      const_cast<std::byte*>(address_buffer.data())));
+  EXPECT_EQ(bytes::ReadInOrder<uint16_t>(std::endian::big, &kRegisterAddress),
+            kActualAddress);
+
+  // Check data.
+  uint8_t* read_pointer = reinterpret_cast<uint8_t*>(&read_data);
+  for (uint32_t i = 0; i < sizeof(read_data); i++) {
+    const uint32_t kReadPointerIndex = sizeof(read_data) - 1 - i;
+    EXPECT_EQ(read_pointer[kReadPointerIndex],
+              static_cast<uint8_t>(register_data[i]));
+  }
+}
+
+}  // namespace
+}  // namespace i2c
+}  // namespace pw