[audio][drivers] Add da7219 codec skeleton

This CL was reviewed in http://fxrev.dev/735864 with minor
updates here.

Bug: 102079
Test: Trace the driver logs "Started" and the I2C controller logs data in/out.
tools/bazel run --config=fuchsia_x64 //src/da7219/controller:pkg.component
tools/bazel run --config=fuchsia_x64 //src/da7219/driver:pkg
tools/ffx log --filter da7219 --filter controller
[src/da7219/controller/i2c_controller.cc:125] Received write address=34
[src/da7219/controller/i2c_controller.cc:126] Received write data=56
[src/da7219/controller/i2c_controller.cc:134] Received write address=78
[src/da7219/controller/i2c_controller.cc:142] Read data to send data=12
[src/da7219/driver/da7219.cc:120] Read byte=12
[src/da7219/driver/da7219.cc:121] Started

Change-Id: If1e35f9f0e2a88bbaf1c08e9a6f29377efbb3d12
diff --git a/src/da7219/README.md b/src/da7219/README.md
new file mode 100644
index 0000000..e661a3c
--- /dev/null
+++ b/src/da7219/README.md
@@ -0,0 +1,54 @@
+# Da7219 audio hardware codec driver
+
+This sample project contains a driver component named `da7219` for an audio hardware codec
+device (Dialog da7219) connected to an I2C bus with device address `0x1A`.
+The driver interacts with the device hardware using the `fuchsia.hardware.i2c` protocol,
+and exposes the Codec FIDL protocol (`fuchsia.hardware.audio/Codec`) for other components
+to consume.
+
+The `i2c_controller` driver component emulates an I2C bus controller, creating the child
+device nodes to the sensor device and responding to I2C protocol commands received from
+the child devices.
+
+## Building
+
+To build the `da7219` driver and related components, run the following commands:
+
+```
+tools/bazel build --config=fuchsia_x64 //src/da7219/controller:pkg
+tools/bazel build --config=fuchsia_x64 //src/da7219/driver:pkg
+```
+
+## Running
+
+Use the following commands to load the driver components on Qemu:
+
+1.  Load the `i2c_controller` driver component to create a virtual I2C device node:
+
+    ```
+    tools/bazel run --config=fuchsia_x64 //src/da7219/controller:pkg.component
+    ```
+
+1.  Load the `da7219` driver component to bind to the device node and begin
+    sending requests:
+
+    ```
+    tools/bazel run --config=fuchsia_x64 //src/da7219/driver:pkg.component
+    ```
+
+1.  Open the device log viewer:
+
+    ```
+    tools/ffx log --filter i2c_controller --filter da7219
+    ```
+
+You should see the `da7219` driver log that it is started after the driver has successfully bound.
+
+```
+[da7219,driver][I]: [da7219.cc:139] Started
+```
+
+## Source layout
+
+*   `//src/da7219/controller` — Source code of the `i2c_controller` driver component.
+*   `//src/da7219/driver/` — Source code of the `da7219` driver component.
diff --git a/src/da7219/controller/BUILD.bazel b/src/da7219/controller/BUILD.bazel
new file mode 100644
index 0000000..85152a1
--- /dev/null
+++ b/src/da7219/controller/BUILD.bazel
@@ -0,0 +1,66 @@
+# Copyright 2022 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+load(
+    "@rules_fuchsia//fuchsia:defs.bzl",
+    "fuchsia_cc_binary",
+    "fuchsia_component_manifest",
+    "fuchsia_driver_bind_bytecode",
+    "fuchsia_driver_component",
+    "fuchsia_package",
+)
+
+fuchsia_driver_bind_bytecode(
+    name = "bind_bytecode",
+    output = "i2c_controller.bindbc",
+    rules = "i2c_controller.bind",
+    deps = [
+        "@fuchsia_sdk//bind/fuchsia.acpi",
+    ],
+)
+
+cc_binary(
+    name = "i2c_controller",
+    srcs = [
+        "i2c_controller.cc",
+        "i2c_controller.h",
+    ],
+    linkshared = True,
+    deps = [
+        "@fuchsia_sdk//fidl/fuchsia.device.fs:fuchsia.device.fs_llcpp_cc",
+        "@fuchsia_sdk//fidl/fuchsia.driver.compat:fuchsia.driver.compat_llcpp_cc",
+        "@fuchsia_sdk//fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_bindlib_cc",
+        "@fuchsia_sdk//fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_llcpp_cc",
+        "@fuchsia_sdk//fidl/zx:zx_cc",
+        "@fuchsia_sdk//pkg/async-cpp",
+        "@fuchsia_sdk//pkg/driver2_cpp",
+        "@fuchsia_sdk//pkg/fidl_cpp_wire",
+        "@fuchsia_sdk//pkg/sys_component_cpp",
+        "@fuchsia_sdk//pkg/zx",
+    ],
+)
+
+fuchsia_component_manifest(
+    name = "manifest",
+    src = "meta/i2c_controller.cml",
+    includes = [
+        "@fuchsia_sdk//pkg/syslog:client",
+    ],
+)
+
+fuchsia_driver_component(
+    name = "component",
+    bind_bytecode = ":bind_bytecode",
+    driver_lib = ":i2c_controller",
+    manifest = ":manifest",
+)
+
+fuchsia_package(
+    name = "pkg",
+    package_name = "i2c_controller",
+    visibility = ["//visibility:public"],
+    deps = [
+        ":component",
+    ],
+)
diff --git a/src/da7219/controller/i2c_controller.bind b/src/da7219/controller/i2c_controller.bind
new file mode 100644
index 0000000..df409a0
--- /dev/null
+++ b/src/da7219/controller/i2c_controller.bind
@@ -0,0 +1,11 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+using fuchsia.acpi;
+
+// Bind to an ACPI node in Qemu (unused one) or in Atlas (the specification for this codec).
+accept fuchsia.acpi.hid {
+  "QEMU0002",
+  "DLGS7219",
+}
diff --git a/src/da7219/controller/i2c_controller.cc b/src/da7219/controller/i2c_controller.cc
new file mode 100644
index 0000000..f7245d3
--- /dev/null
+++ b/src/da7219/controller/i2c_controller.cc
@@ -0,0 +1,139 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "i2c_controller.h"
+
+#include <endian.h>
+#include <fidl/fuchsia.component.decl/cpp/wire.h>
+#include <lib/driver2/record_cpp.h>
+
+#include <bind/fuchsia/hardware/i2c/cpp/bind.h>
+
+namespace test_i2c_controller {
+
+zx::status<std::unique_ptr<TestDa7219I2cController>> TestDa7219I2cController::Start(
+    fuchsia_driver_framework::wire::DriverStartArgs& start_args, fdf::UnownedDispatcher dispatcher,
+    fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+    driver::Logger logger) {
+  auto driver = std::make_unique<TestDa7219I2cController>(
+      dispatcher->async_dispatcher(), std::move(node), std::move(ns), std::move(logger));
+  auto result = driver->Run(std::move(start_args.outgoing_dir()));
+  if (result.is_error()) {
+    return result.take_error();
+  }
+
+  return zx::ok(std::move(driver));
+}
+
+zx::status<> TestDa7219I2cController::Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir) {
+  auto service = [this](fidl::ServerEnd<fuchsia_hardware_i2c::Device> server_end) {
+    fidl::BindServer(dispatcher_, std::move(server_end), this);
+  };
+
+  auto status = outgoing_.AddProtocol<fuchsia_hardware_i2c::Device>(std::move(service));
+  if (status.is_error()) {
+    FDF_SLOG(ERROR, "Failed to add protocol", KV("status", status.status_string()));
+    return status;
+  }
+
+  status = AddChild();
+  if (status.is_error()) {
+    return status;
+  }
+
+  return outgoing_.Serve(std::move(outgoing_dir));
+}
+
+zx::status<> TestDa7219I2cController::AddChild() {
+  fidl::Arena arena;
+
+  // Offer `fuchsia.hardware.i2c.Device` to the driver that binds to the node.
+  auto protocol_offer =
+      fuchsia_component_decl::wire::OfferProtocol::Builder(arena)
+          .source_name(arena, fidl::DiscoverableProtocolName<fuchsia_hardware_i2c::Device>)
+          .target_name(arena, fidl::DiscoverableProtocolName<fuchsia_hardware_i2c::Device>)
+          .dependency_type(fuchsia_component_decl::wire::DependencyType::kStrong)
+          .Build();
+  fuchsia_component_decl::wire::Offer offer =
+      fuchsia_component_decl::wire::Offer::WithProtocol(arena, protocol_offer);
+
+  // Set the properties of the node that a driver will bind to.
+  auto properties = fidl::VectorView<fuchsia_driver_framework::wire::NodeProperty>(arena, 2);
+  properties[0] = fuchsia_driver_framework::wire::NodeProperty::Builder(arena)
+                      .key(fuchsia_driver_framework::wire::NodePropertyKey::WithStringValue(
+                          arena, bind_fuchsia_hardware_i2c::DEVICE))
+                      .value(fuchsia_driver_framework::wire::NodePropertyValue::WithEnumValue(
+                          arena, bind_fuchsia_hardware_i2c::DEVICE_ZIRCONTRANSPORT))
+                      .Build();
+  properties[1] = fuchsia_driver_framework::wire::NodeProperty::Builder(arena)
+                      .key(fuchsia_driver_framework::wire::NodePropertyKey::WithIntValue(
+                          0x0A02 /* BIND_I2C_ADDRESS */))
+                      .value(fuchsia_driver_framework::wire::NodePropertyValue::WithIntValue(
+                          0x1a /* I2C address used by the DA7219 codec */))
+                      .Build();
+
+  auto args =
+      fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
+          .name(arena, "i2c-child")
+          .offers(fidl::VectorView<fuchsia_component_decl::wire::Offer>::FromExternal(&offer, 1))
+          .properties(properties)
+          .Build();
+
+  // Create endpoints of the `NodeController` for the node.
+  auto endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
+  if (endpoints.is_error()) {
+    FDF_SLOG(ERROR, "Failed to create endpoint", KV("status", endpoints.status_string()));
+    return zx::error(endpoints.status_value());
+  }
+
+  auto result = node_.sync()->AddChild(args, std::move(endpoints->server), {});
+  if (!result.ok()) {
+    FDF_SLOG(ERROR, "Failed to add child", KV("status", result.status_string()));
+    return zx::error(result.status());
+  }
+
+  return zx::ok();
+}
+
+// Protocol method of `fuchsia.hardware.i2c.Device` to handle data transfer requests.
+void TestDa7219I2cController::Transfer(TransferRequestView request,
+                                       TransferCompleter::Sync& completer) {
+  FDF_SLOG(DEBUG, "Received transfer request");
+
+  // Must be either a write transfer (2 bytes write) or
+  // a read transfer (1 byte write and 1 byte read).
+  if (request->transactions.count() == 1) {  // Write transfer.
+    ZX_ASSERT(request->transactions[0].has_data_transfer());
+    const auto& transfer = request->transactions[0].data_transfer();
+    ZX_ASSERT(transfer.is_write_data() && !transfer.is_read_size());
+    ZX_ASSERT(transfer.write_data().count() == 2);
+    FDF_SLOG(INFO, "Received write", KV("address", static_cast<int>(transfer.write_data()[0])));
+    FDF_SLOG(INFO, "Received write", KV("data", static_cast<int>(transfer.write_data()[1])));
+    completer.ReplySuccess({});
+  } else if (request->transactions.count() == 2) {  // Read transfer.
+    ZX_ASSERT(request->transactions[0].has_data_transfer());
+    const auto& transfer0 = request->transactions[0].data_transfer();
+    ZX_ASSERT(transfer0.is_write_data() && !transfer0.is_read_size());
+    ZX_ASSERT(transfer0.write_data().count() == 1);
+    FDF_SLOG(INFO, "Received write", KV("address", static_cast<int>(transfer0.write_data()[0])));
+    const auto& transfer1 = request->transactions[1].data_transfer();
+    ZX_ASSERT(!transfer1.is_write_data() && transfer1.is_read_size());
+    ZX_ASSERT(transfer1.read_size() == 1);
+    fidl::Arena allocator;
+    auto read_bytes = fidl::VectorView<uint8_t>(allocator, 1);
+    // Test read by sending arbitrary values.
+    constexpr uint8_t kTestArbitraryDataToRead = 12;
+    read_bytes[0] = kTestArbitraryDataToRead;
+    FDF_SLOG(INFO, "Read data to send", KV("data", static_cast<int>(read_bytes[0])));
+    auto read_results = fidl::VectorView<fidl::VectorView<uint8_t>>(allocator, 1);
+    read_results[0] = read_bytes;
+    completer.ReplySuccess(read_results);
+  } else {
+    completer.ReplyError(ZX_ERR_INVALID_ARGS);
+  }
+}
+
+}  // namespace test_i2c_controller
+
+FUCHSIA_DRIVER_RECORD_CPP_V1(test_i2c_controller::TestDa7219I2cController);
diff --git a/src/da7219/controller/i2c_controller.h b/src/da7219/controller/i2c_controller.h
new file mode 100644
index 0000000..6dfc975
--- /dev/null
+++ b/src/da7219/controller/i2c_controller.h
@@ -0,0 +1,60 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_SDK_EXAMPLES_SRC_DA7219_CONTROLLER_I2C_CONTROLLER_H_
+#define FUCHSIA_SDK_EXAMPLES_SRC_DA7219_CONTROLLER_I2C_CONTROLLER_H_
+
+#include <fidl/fuchsia.driver.framework/cpp/wire.h>
+#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+#include <lib/async/cpp/executor.h>
+#include <lib/driver2/namespace.h>
+#include <lib/driver2/structured_logger.h>
+#include <lib/fdf/cpp/dispatcher.h>
+#include <lib/sys/component/cpp/outgoing_directory.h>
+
+namespace test_i2c_controller {
+
+// Sample driver that implements a `fuchsia.hardware.i2c` bus controller and emulates the
+// behavior of an audio hardware codec device to interact with the da7219 driver.
+class TestDa7219I2cController : public fidl::WireServer<fuchsia_hardware_i2c::Device> {
+ public:
+  TestDa7219I2cController(async_dispatcher_t* dispatcher,
+                          fidl::WireSharedClient<fuchsia_driver_framework::Node> node,
+                          driver::Namespace ns, driver::Logger logger)
+      : dispatcher_(dispatcher),
+        outgoing_(component::OutgoingDirectory::Create(dispatcher)),
+        ns_(std::move(ns)),
+        logger_(std::move(logger)),
+        node_(std::move(node)) {}
+
+  virtual ~TestDa7219I2cController() = default;
+
+  static constexpr const char* Name() { return "test-da7219-i2c-controller"; }
+
+  static zx::status<std::unique_ptr<TestDa7219I2cController>> Start(
+      fuchsia_driver_framework::wire::DriverStartArgs& start_args,
+      fdf::UnownedDispatcher dispatcher,
+      fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+      driver::Logger logger);
+
+  // fuchsia.hardware.i2c/Device:
+  void Transfer(TransferRequestView request, TransferCompleter::Sync& completer);
+
+ private:
+  zx::status<> Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir);
+  zx::status<> AddChild();
+
+  async_dispatcher_t* const dispatcher_;
+
+  component::OutgoingDirectory outgoing_;
+  driver::Namespace ns_;
+  driver::Logger logger_;
+
+  fidl::WireSharedClient<fuchsia_driver_framework::Node> node_;
+  fidl::WireSharedClient<fuchsia_driver_framework::NodeController> controller_;
+};
+
+}  // namespace test_i2c_controller
+
+#endif  // FUCHSIA_SDK_EXAMPLES_SRC_DA7219_CONTROLLER_I2C_CONTROLLER_H_
diff --git a/src/da7219/controller/meta/i2c_controller.cml b/src/da7219/controller/meta/i2c_controller.cml
new file mode 100644
index 0000000..817eeb6
--- /dev/null
+++ b/src/da7219/controller/meta/i2c_controller.cml
@@ -0,0 +1,27 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+{
+    include: [
+        'syslog/client.shard.cml',
+    ],
+    program: {
+        runner: 'driver',
+        binary: 'lib/libi2c_controller.so',
+        bind: 'meta/bind/i2c_controller.bindbc'
+    },
+    use: [
+        { service: 'fuchsia.driver.compat.Service' },
+    ],
+    // Provide the I2C device capability to other components
+    // TODO(fxbug.dev/106665): Route as a service capability from the SDK
+    capabilities: [
+        { protocol: 'fuchsia.hardware.i2c.Device' },
+    ],
+    expose: [
+        {
+            protocol: 'fuchsia.hardware.i2c.Device',
+            from: 'self',
+        },
+    ],
+}
diff --git a/src/da7219/driver/BUILD.bazel b/src/da7219/driver/BUILD.bazel
new file mode 100644
index 0000000..dbda248
--- /dev/null
+++ b/src/da7219/driver/BUILD.bazel
@@ -0,0 +1,66 @@
+# Copyright 2022 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+load(
+    "@rules_fuchsia//fuchsia:defs.bzl",
+    "fuchsia_cc_binary",
+    "fuchsia_component_manifest",
+    "fuchsia_driver_bind_bytecode",
+    "fuchsia_driver_component",
+    "fuchsia_package",
+)
+
+fuchsia_component_manifest(
+    name = "manifest",
+    src = "meta/da7219.cml",
+    includes = [
+        "@fuchsia_sdk//pkg/syslog:client",
+    ],
+)
+
+fuchsia_driver_bind_bytecode(
+    name = "bind_bytecode",
+    output = "da7219.bindbc",
+    rules = "da7219.bind",
+    deps = [
+        "@fuchsia_sdk//fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_bindlib",
+    ],
+)
+
+cc_binary(
+    name = "da7219",
+    srcs = [
+        "da7219.cc",
+        "da7219.h",
+        "i2c_channel.cc",
+        "i2c_channel.h",
+    ],
+    linkshared = True,
+    deps = [
+        "@fuchsia_sdk//fidl/fuchsia.hardware.audio:fuchsia.hardware.audio_llcpp_cc",
+        "@fuchsia_sdk//fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_llcpp_cc",
+        "@fuchsia_sdk//fidl/zx:zx_cc",
+        "@fuchsia_sdk//pkg/async-cpp",
+        "@fuchsia_sdk//pkg/driver2_cpp",
+        "@fuchsia_sdk//pkg/fidl_cpp_wire",
+        "@fuchsia_sdk//pkg/sys_component_cpp",
+        "@fuchsia_sdk//pkg/zx",
+    ],
+)
+
+fuchsia_driver_component(
+    name = "component",
+    bind_bytecode = ":bind_bytecode",
+    driver_lib = ":da7219",
+    manifest = ":manifest",
+)
+
+fuchsia_package(
+    name = "pkg",
+    package_name = "da7219",
+    visibility = ["//visibility:public"],
+    deps = [
+        ":component",
+    ],
+)
diff --git a/src/da7219/driver/da7219.bind b/src/da7219/driver/da7219.bind
new file mode 100644
index 0000000..c39457c
--- /dev/null
+++ b/src/da7219/driver/da7219.bind
@@ -0,0 +1,8 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+using fuchsia.hardware.i2c;
+
+fuchsia.hardware.i2c.Device == fuchsia.hardware.i2c.Device.ZirconTransport;
+fuchsia.BIND_I2C_ADDRESS == 0x1a;
diff --git a/src/da7219/driver/da7219.cc b/src/da7219/driver/da7219.cc
new file mode 100644
index 0000000..a1fdf15
--- /dev/null
+++ b/src/da7219/driver/da7219.cc
@@ -0,0 +1,145 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "da7219.h"
+
+namespace {
+
+// FIDL server implementation for the `fuchsia.examples.audio/Device` protocol
+class Da7219Server : public fidl::WireServer<fuchsia_hardware_audio::Codec> {
+ public:
+  explicit Da7219Server(driver::Namespace& ns, async_dispatcher_t* dispatcher,
+                        std::weak_ptr<I2cChannel> i2c_channel)
+      : i2c_channel_(i2c_channel) {
+    auto logger_result = driver::Logger::Create(ns, dispatcher, "da7219-server");
+    ZX_ASSERT(logger_result.is_ok());
+    logger_ = std::move(*logger_result);
+  }
+
+  // Handle incoming connection requests from FIDL clients
+  static fidl::ServerBindingRef<fuchsia_hardware_audio::Codec> BindDeviceClient(
+      driver::Namespace& ns, async_dispatcher_t* dispatcher, std::weak_ptr<I2cChannel> i2c_channel,
+      fidl::ServerEnd<fuchsia_hardware_audio::Codec> request) {
+    // Bind each connection request to a unique FIDL server instance
+    auto server_impl = std::make_unique<Da7219Server>(ns, dispatcher, i2c_channel);
+    return fidl::BindServer(dispatcher, std::move(request), std::move(server_impl),
+                            std::mem_fn(&Da7219Server::OnUnbound));
+  }
+
+  // This method is called when a server connection is torn down.
+  void OnUnbound(fidl::UnbindInfo info, fidl::ServerEnd<fuchsia_hardware_audio::Codec> server_end) {
+    if (info.is_peer_closed()) {
+      FDF_LOG(DEBUG, "Client disconnected");
+    } else if (!info.is_user_initiated()) {
+      FDF_LOG(ERROR, "Client connection unbound: %s", info.status_string());
+    }
+  }
+
+  // LLCPP implementation for the Codec API.
+  void Reset(ResetCompleter::Sync& completer) override {}
+  void Stop(StopCompleter::Sync& completer) override {}
+  void Start(StartCompleter::Sync& completer) override {}
+  void GetInfo(GetInfoCompleter::Sync& completer) override {}
+  void GetHealthState(GetHealthStateCompleter::Sync& completer) override {}
+  void IsBridgeable(IsBridgeableCompleter::Sync& completer) override {}
+  void SetBridgedMode(SetBridgedModeRequestView request,
+                      SetBridgedModeCompleter::Sync& completer) override {}
+  void GetDaiFormats(GetDaiFormatsCompleter::Sync& completer) override {}
+  void SetDaiFormat(SetDaiFormatRequestView request,
+                    SetDaiFormatCompleter::Sync& completer) override {}
+  void GetPlugDetectCapabilities(GetPlugDetectCapabilitiesCompleter::Sync& completer) override {}
+  void WatchPlugState(WatchPlugStateCompleter::Sync& completer) override {}
+  void SignalProcessingConnect(SignalProcessingConnectRequestView request,
+                               SignalProcessingConnectCompleter::Sync& completer) override {}
+
+ private:
+  driver::Logger logger_;
+  std::weak_ptr<I2cChannel> i2c_channel_;
+};
+
+}  // namespace
+
+namespace audio {
+
+// static
+zx::status<std::unique_ptr<Da7219Driver>> Da7219Driver::Start(
+    fuchsia_driver_framework::wire::DriverStartArgs& start_args, fdf::UnownedDispatcher dispatcher,
+    fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+    driver::Logger logger) {
+  auto driver = std::make_unique<Da7219Driver>(dispatcher->async_dispatcher(), std::move(node),
+                                               std::move(ns), std::move(logger));
+  auto result = driver->Run(std::move(start_args.outgoing_dir()));
+  if (result.is_error()) {
+    return result.take_error();
+  }
+
+  return zx::ok(std::move(driver));
+}
+
+zx::status<> Da7219Driver::Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir) {
+  auto channel_result = SetupI2cChannel();
+  if (channel_result.is_error()) {
+    return channel_result.take_error();
+  }
+
+  // Serve the fuchsia.hardware.audio/Codec protocol to clients through the
+  // fuchsia.hardware.audio/CodecService wrapper.
+  component::ServiceHandler handler;
+  fuchsia_hardware_audio::CodecService::Handler service(&handler);
+
+  auto result =
+      service.add_codec([this](fidl::ServerEnd<fuchsia_hardware_audio::Codec> request) -> void {
+        Da7219Server::BindDeviceClient(ns_, dispatcher_, i2c_channel_, std::move(request));
+      });
+  ZX_ASSERT(result.is_ok());
+
+  result = outgoing_.AddService<fuchsia_hardware_audio::CodecService>(std::move(handler));
+  if (result.is_error()) {
+    FDF_SLOG(ERROR, "Failed to add service", KV("status", result.status_string()));
+    return result.take_error();
+  }
+
+  auto status = outgoing_.Serve(std::move(outgoing_dir));
+  if (status.is_error()) {
+    FDF_SLOG(ERROR, "Failed to serve outgoing directory", KV("status", status.status_string()));
+    return status.take_error();
+  }
+
+  // Test write and read by sending arbitrary values.
+  constexpr uint8_t kTestArbitraryAddress0 = 34;
+  constexpr uint8_t kTestArbitraryDataToWrite0 = 56;
+  constexpr uint8_t kTestArbitraryAddress1 = 78;
+  auto write_status = i2c_channel_->Write(kTestArbitraryAddress0, kTestArbitraryDataToWrite0);
+  if (write_status.is_error()) {
+    FDF_SLOG(ERROR, "Failed to write to I2C", KV("status", write_status.status_string()));
+    return write_status.take_error();
+  }
+  auto i2c_read = i2c_channel_->Read(kTestArbitraryAddress1);
+  if (i2c_read.is_error()) {
+    FDF_SLOG(ERROR, "Failed to read from I2C", KV("status", i2c_read.status_string()));
+    return i2c_read.take_error();
+  }
+  FDF_SLOG(INFO, "Read", KV("byte", static_cast<int>(i2c_read.value())));
+
+  FDF_SLOG(INFO, "Started");
+
+  return zx::ok();
+}
+
+// Connect to the `fuchsia.hardware.i2c/Device` protocol offered by the parent device node.
+zx::status<> Da7219Driver::SetupI2cChannel() {
+  auto client_endpoint = ns_.Connect<fuchsia_hardware_i2c::Device>();
+  if (client_endpoint.status_value() != ZX_OK) {
+    FDF_SLOG(ERROR, "Failed to setup I2cChannel", KV("status", client_endpoint.status_string()));
+    return client_endpoint.take_error();
+  }
+
+  auto i2c_client = fidl::WireClient(std::move(*client_endpoint), dispatcher_);
+  i2c_channel_ = std::make_shared<I2cChannel>(std::move(i2c_client));
+  return zx::ok();
+}
+
+}  // namespace audio
+
+FUCHSIA_DRIVER_RECORD_CPP_V1(audio::Da7219Driver);
diff --git a/src/da7219/driver/da7219.h b/src/da7219/driver/da7219.h
new file mode 100644
index 0000000..5a53c00
--- /dev/null
+++ b/src/da7219/driver/da7219.h
@@ -0,0 +1,57 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_SDK_EXAMPLES_SRC_DA7219_DRIVER_DA7219_H_
+#define FUCHSIA_SDK_EXAMPLES_SRC_DA7219_DRIVER_DA7219_H_
+
+#include <fidl/fuchsia.driver.framework/cpp/wire.h>
+#include <fidl/fuchsia.hardware.audio/cpp/wire.h>
+#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+#include <lib/driver2/namespace.h>
+#include <lib/driver2/record_cpp.h>
+#include <lib/driver2/structured_logger.h>
+#include <lib/fdf/cpp/dispatcher.h>
+#include <lib/sys/component/cpp/outgoing_directory.h>
+#include <lib/zx/status.h>
+
+#include "i2c_channel.h"
+
+namespace audio {
+
+class Da7219Driver {
+ public:
+  Da7219Driver(async_dispatcher_t* dispatcher,
+               fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+               driver::Logger logger)
+      : dispatcher_(dispatcher),
+        outgoing_(component::OutgoingDirectory::Create(dispatcher)),
+        node_(std::move(node)),
+        ns_(std::move(ns)),
+        logger_(std::move(logger)) {}
+
+  virtual ~Da7219Driver() = default;
+
+  static constexpr const char* Name() { return "da7219"; }
+
+  static zx::status<std::unique_ptr<Da7219Driver>> Start(
+      fuchsia_driver_framework::wire::DriverStartArgs& start_args,
+      fdf::UnownedDispatcher dispatcher,
+      fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+      driver::Logger logger);
+
+ private:
+  zx::status<> Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir);
+  zx::status<> SetupI2cChannel();
+
+  async_dispatcher_t* const dispatcher_;
+  component::OutgoingDirectory outgoing_;
+  fidl::WireSharedClient<fuchsia_driver_framework::Node> node_;
+  driver::Namespace ns_;
+  driver::Logger logger_;
+  std::shared_ptr<I2cChannel> i2c_channel_;
+};
+
+}  // namespace audio
+
+#endif  // FUCHSIA_SDK_EXAMPLES_SRC_DA7219_DRIVER_DA7219_H_
diff --git a/src/da7219/driver/i2c_channel.cc b/src/da7219/driver/i2c_channel.cc
new file mode 100644
index 0000000..2b2e7ec
--- /dev/null
+++ b/src/da7219/driver/i2c_channel.cc
@@ -0,0 +1,82 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "i2c_channel.h"
+
+#include <endian.h>
+
+zx::status<uint8_t> I2cChannel::Read(uint8_t address) {
+  uint8_t value;
+  auto status = WriteReadSync(&address, sizeof(address), &value, sizeof(value));
+
+  if (status.is_error()) {
+    return status.take_error();
+  }
+
+  return zx::ok(value);
+}
+
+zx::status<> I2cChannel::Write(uint8_t address, uint8_t value) {
+  uint8_t data[2];
+  data[0] = address;
+  data[1] = value;
+  return WriteReadSync(reinterpret_cast<uint8_t*>(&data), sizeof(data), nullptr, 0);
+}
+
+zx::status<> I2cChannel::WriteReadSync(const uint8_t* tx_buf, size_t tx_len, uint8_t* rx_buf,
+                                       size_t rx_len) {
+  if (tx_len > fuchsia_hardware_i2c::wire::kMaxTransferSize ||
+      rx_len > fuchsia_hardware_i2c::wire::kMaxTransferSize) {
+    return zx::error(ZX_ERR_OUT_OF_RANGE);
+  }
+
+  fidl::Arena arena;
+  fidl::VectorView<uint8_t> write_data(arena, tx_len);
+  if (tx_len) {
+    memcpy(write_data.data(), tx_buf, tx_len);
+  }
+
+  auto write_transfer = fuchsia_hardware_i2c::wire::DataTransfer::WithWriteData(arena, write_data);
+  auto read_transfer =
+      fuchsia_hardware_i2c::wire::DataTransfer::WithReadSize(static_cast<uint32_t>(rx_len));
+
+  fuchsia_hardware_i2c::wire::Transaction transactions[2];
+  size_t index = 0;
+  if (tx_len > 0) {
+    transactions[index++] = fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
+                                .data_transfer(write_transfer)
+                                .Build();
+  }
+
+  if (rx_len > 0) {
+    transactions[index++] = fuchsia_hardware_i2c::wire::Transaction::Builder(arena)
+                                .data_transfer(read_transfer)
+                                .Build();
+  }
+
+  if (index == 0) {
+    return zx::error(ZX_ERR_INVALID_ARGS);
+  }
+
+  const auto reply = fidl_client_.sync()->Transfer(
+      fidl::VectorView<fuchsia_hardware_i2c::wire::Transaction>::FromExternal(transactions, index));
+  if (!reply.ok()) {
+    return zx::error(reply.status());
+  }
+  if (reply->is_error()) {
+    return zx::error(reply->error_value());
+  }
+
+  if (rx_len > 0) {
+    const auto& read_data = reply->value()->read_data;
+    // Truncate the returned buffer to match the behavior of the Banjo version.
+    if (read_data.count() != 1) {
+      return zx::error(ZX_ERR_IO);
+    }
+
+    memcpy(rx_buf, read_data[0].data(), std::min(rx_len, read_data[0].count()));
+  }
+
+  return zx::ok();
+}
diff --git a/src/da7219/driver/i2c_channel.h b/src/da7219/driver/i2c_channel.h
new file mode 100644
index 0000000..7b866bc
--- /dev/null
+++ b/src/da7219/driver/i2c_channel.h
@@ -0,0 +1,31 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FUCHSIA_SDK_EXAMPLES_SRC_DA7219_DRIVER_I2C_CHANNEL_H_
+#define FUCHSIA_SDK_EXAMPLES_SRC_DA7219_DRIVER_I2C_CHANNEL_H_
+
+#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+
+// Sends read and write transactions through a fuchsia.hardware.i2c client.
+class I2cChannel {
+ public:
+  explicit I2cChannel(fidl::WireClient<fuchsia_hardware_i2c::Device> client_end)
+      : fidl_client_(std::move(client_end)) {}
+
+  I2cChannel(I2cChannel&& other) noexcept = default;
+  I2cChannel& operator=(I2cChannel&& other) noexcept = default;
+
+  ~I2cChannel() = default;
+
+  // Functions that perform read/write transactions through the client.
+  zx::status<uint8_t> Read(uint8_t address);
+  zx::status<> Write(uint8_t address, uint8_t value);
+
+ private:
+  zx::status<> WriteReadSync(const uint8_t* tx_buf, size_t tx_len, uint8_t* rx_buf, size_t rx_len);
+
+  fidl::WireClient<fuchsia_hardware_i2c::Device> fidl_client_;
+};
+
+#endif  // FUCHSIA_SDK_EXAMPLES_SRC_DA7219_DRIVER_I2C_CHANNEL_H_
diff --git a/src/da7219/driver/meta/da7219.cml b/src/da7219/driver/meta/da7219.cml
new file mode 100644
index 0000000..61218c5
--- /dev/null
+++ b/src/da7219/driver/meta/da7219.cml
@@ -0,0 +1,18 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+{
+    include: [
+        'syslog/client.shard.cml',
+    ],
+    program: {
+        runner: 'driver',
+        binary: 'lib/libda7219.so',
+        bind: 'meta/bind/da7219.bindbc'
+    },
+    // Consume the I2C device capability from the parent
+    use: [
+        { protocol: 'fuchsia.hardware.i2c.Device' },
+        { service: 'fuchsia.driver.compat.Service' },
+    ],
+}
\ No newline at end of file