[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