[audio][as370] Add max98373 codecs and controller

Test: max98373 unit tests and trace I2C transactions on init and gain changes
Change-Id: Iec959e9396a60b47d97f90d6aec8da854d654cd5
diff --git a/zircon/system/dev/audio/BUILD.gn b/zircon/system/dev/audio/BUILD.gn
index 7e1107f..8fb7e3a 100644
--- a/zircon/system/dev/audio/BUILD.gn
+++ b/zircon/system/dev/audio/BUILD.gn
@@ -4,6 +4,7 @@
 
 group("audio") {
   deps = [
+    "as370-tdm-output",
     "astro-pdm-input",
     "astro-tdm-output",
     "codecs",
diff --git a/zircon/system/dev/audio/as370-tdm-output/BUILD.gn b/zircon/system/dev/audio/as370-tdm-output/BUILD.gn
new file mode 100644
index 0000000..2c851e8
--- /dev/null
+++ b/zircon/system/dev/audio/as370-tdm-output/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2019 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.
+
+driver("as370-tdm-output") {
+  sources = [
+    "audio-stream-out.cpp",
+    "codec.cpp",
+  ]
+  deps = [
+    "$zx/system/banjo/ddk.protocol.codec",
+    "$zx/system/banjo/ddk.protocol.composite",
+    "$zx/system/banjo/ddk.protocol.platform.device",
+    "$zx/system/dev/audio/lib/codec-interface",
+    "$zx/system/dev/audio/lib/simple-audio-stream",
+    "$zx/system/dev/lib/mmio",
+    "$zx/system/dev/lib/as370",
+    "$zx/system/ulib/audio-driver-proto",
+    "$zx/system/ulib/audio-proto-utils",
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/ddktl",
+    "$zx/system/ulib/dispatcher-pool",
+    "$zx/system/ulib/fbl",
+    "$zx/system/ulib/fzl",
+    "$zx/system/ulib/hwreg",
+    "$zx/system/ulib/sync",
+    "$zx/system/ulib/zircon",
+    "$zx/system/ulib/zx",
+  ]
+}
diff --git a/zircon/system/dev/audio/as370-tdm-output/audio-stream-out.cpp b/zircon/system/dev/audio/as370-tdm-output/audio-stream-out.cpp
new file mode 100644
index 0000000..44faf75
--- /dev/null
+++ b/zircon/system/dev/audio/as370-tdm-output/audio-stream-out.cpp
@@ -0,0 +1,171 @@
+// Copyright 2019 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 "audio-stream-out.h"
+
+#include <optional>
+#include <utility>
+
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/driver.h>
+#include <ddk/metadata.h>
+#include <ddk/platform-defs.h>
+#include <ddk/protocol/composite.h>
+#include <ddktl/metadata/audio.h>
+#include <fbl/array.h>
+
+namespace {
+
+enum {
+    COMPONENT_PDEV,
+    COMPONENT_CODEC,
+    COMPONENT_COUNT,
+};
+
+} // namespace
+
+namespace audio {
+namespace as370 {
+
+As370AudioStreamOut::As370AudioStreamOut(zx_device_t* parent)
+    : SimpleAudioStream(parent, false), pdev_(parent) {
+}
+
+zx_status_t As370AudioStreamOut::InitPdev() {
+    composite_protocol_t composite;
+
+    auto status = device_get_protocol(parent(), ZX_PROTOCOL_COMPOSITE, &composite);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "Could not get composite protocol\n");
+        return status;
+    }
+
+    zx_device_t* components[COMPONENT_COUNT] = {};
+    size_t actual;
+    composite_get_components(&composite, components, countof(components), &actual);
+    // Only PDEV and I2C components are required.
+    if (actual < 2) {
+        zxlogf(ERROR, "could not get components\n");
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+
+    pdev_ = components[COMPONENT_PDEV];
+    if (!pdev_.is_valid()) {
+        return ZX_ERR_NO_RESOURCES;
+    }
+
+    codec_.proto_client_ = components[COMPONENT_CODEC];
+    if (!pdev_.is_valid()) {
+        return ZX_ERR_NO_RESOURCES;
+    }
+
+    // TODO(andresoportus) configure controller and codec per codec protocol.
+    return ZX_OK;
+}
+
+zx_status_t As370AudioStreamOut::Init() {
+    auto status = InitPdev();
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    // Get our gain capabilities.
+    gain_state_t state = {};
+    status = codec_.GetGainState(&state);
+    if (status != ZX_OK) {
+        return status;
+    }
+    cur_gain_state_.cur_gain = state.gain;
+    cur_gain_state_.cur_mute = state.muted;
+    cur_gain_state_.cur_agc = state.agc_enable;
+
+    gain_format_t format = {};
+    status = codec_.GetGainFormat(&format);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    cur_gain_state_.min_gain = format.min_gain;
+    cur_gain_state_.max_gain = format.max_gain;
+    cur_gain_state_.gain_step = format.gain_step;
+    cur_gain_state_.can_mute = false;
+    cur_gain_state_.can_agc = false;
+
+    snprintf(device_name_, sizeof(device_name_), "as370-audio-out");
+    snprintf(mfr_name_, sizeof(mfr_name_), "unknown");
+    snprintf(prod_name_, sizeof(prod_name_), "as370");
+
+    unique_id_ = AUDIO_STREAM_UNIQUE_ID_BUILTIN_SPEAKERS;
+
+    return ZX_OK;
+}
+
+zx_status_t As370AudioStreamOut::ChangeFormat(const audio_proto::StreamSetFmtReq& req) {
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t As370AudioStreamOut::GetBuffer(const audio_proto::RingBufGetBufferReq& req,
+                                            uint32_t* out_num_rb_frames,
+                                            zx::vmo* out_buffer) {
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t As370AudioStreamOut::Start(uint64_t* out_start_time) {
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t As370AudioStreamOut::Stop() {
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t As370AudioStreamOut::SetGain(const audio_proto::SetGainReq& req) {
+    gain_state_t state;
+    state.gain = req.gain;
+    state.muted = cur_gain_state_.cur_mute;
+    state.agc_enable = cur_gain_state_.cur_agc;
+    auto status = codec_.SetGainState(&state);
+    if (status != ZX_OK) {
+        return status;
+    }
+    cur_gain_state_.cur_gain = state.gain;
+    return ZX_OK;
+}
+
+void As370AudioStreamOut::ShutdownHook() {
+}
+
+zx_status_t As370AudioStreamOut::InitPost() {
+    return ZX_OK;
+}
+
+} // namespace as370
+} // namespace audio
+
+static zx_status_t syn_audio_out_bind(void* ctx, zx_device_t* device) {
+    auto stream =
+        audio::SimpleAudioStream::Create<audio::as370::As370AudioStreamOut>(device);
+    if (stream == nullptr) {
+        return ZX_ERR_NO_MEMORY;
+    }
+
+    return ZX_OK;
+}
+
+static constexpr zx_driver_ops_t syn_audio_out_driver_ops = []() {
+    zx_driver_ops_t ops = {};
+    ops.version = DRIVER_OPS_VERSION;
+    ops.bind = syn_audio_out_bind;
+    return ops;
+}();
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(as370_audio_out, syn_audio_out_driver_ops, "zircon", "0.1", 4)
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_SYNAPTICS),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_SYNAPTICS_AS370),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_SYNAPTICS_AUDIO_OUT),
+ZIRCON_DRIVER_END(as370_audio_out)
+// clang-format on
+
diff --git a/zircon/system/dev/audio/as370-tdm-output/audio-stream-out.h b/zircon/system/dev/audio/as370-tdm-output/audio-stream-out.h
new file mode 100644
index 0000000..4900258
--- /dev/null
+++ b/zircon/system/dev/audio/as370-tdm-output/audio-stream-out.h
@@ -0,0 +1,60 @@
+// Copyright 2019 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.
+
+#pragma once
+
+#include <memory>
+#include <optional>
+
+#include <audio-proto/audio-proto.h>
+#include <ddk/io-buffer.h>
+#include <ddk/protocol/i2c.h>
+#include <ddk/protocol/platform/device.h>
+#include <ddktl/device-internal.h>
+#include <ddktl/device.h>
+#include <ddktl/pdev.h>
+#include <ddktl/protocol/codec.h>
+#include <ddktl/protocol/platform/device.h>
+#include <fbl/mutex.h>
+#include <lib/fzl/pinned-vmo.h>
+#include <lib/simple-audio-stream/simple-audio-stream.h>
+#include <lib/sync/completion.h>
+#include <zircon/thread_annotations.h>
+
+#include "codec.h"
+
+namespace audio {
+namespace as370 {
+
+class As370AudioStreamOut : public SimpleAudioStream {
+
+protected:
+    zx_status_t Init() TA_REQ(domain_->token()) override;
+    zx_status_t ChangeFormat(const audio_proto::StreamSetFmtReq& req)
+        TA_REQ(domain_->token()) override;
+    zx_status_t GetBuffer(const audio_proto::RingBufGetBufferReq& req,
+                          uint32_t* out_num_rb_frames,
+                          zx::vmo* out_buffer) TA_REQ(domain_->token()) override;
+    zx_status_t Start(uint64_t* out_start_time) TA_REQ(domain_->token()) override;
+    zx_status_t Stop() TA_REQ(domain_->token()) override;
+    zx_status_t SetGain(const audio_proto::SetGainReq& req)
+        TA_REQ(domain_->token()) override;
+    void ShutdownHook() TA_REQ(domain_->token()) override;
+    zx_status_t InitPost() TA_REQ(domain_->token()) override;
+
+private:
+    friend class SimpleAudioStream;
+    friend class fbl::RefPtr<As370AudioStreamOut>;
+
+    As370AudioStreamOut(zx_device_t* parent);
+    ~As370AudioStreamOut() {}
+
+    zx_status_t InitPdev() TA_REQ(domain_->token());
+
+    ddk::PDev pdev_ TA_GUARDED(domain_->token());
+    Codec codec_;
+};
+
+} // namespace as370
+} // namespace audio
diff --git a/zircon/system/dev/audio/as370-tdm-output/codec.cpp b/zircon/system/dev/audio/as370-tdm-output/codec.cpp
new file mode 100644
index 0000000..d936e09
--- /dev/null
+++ b/zircon/system/dev/audio/as370-tdm-output/codec.cpp
@@ -0,0 +1,56 @@
+// Copyright 2019 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 "codec.h"
+
+namespace audio {
+namespace as370 {
+
+zx_status_t Codec::GetGainFormat(gain_format_t* format) {
+    struct AsyncOut {
+        sync_completion_t completion;
+        gain_format_t format;
+    } out;
+    proto_client_.GetGainFormat(
+        [](void* ctx, const gain_format_t* format) {
+            auto* out = reinterpret_cast<AsyncOut*>(ctx);
+            out->format = *format;
+            sync_completion_signal(&out->completion);
+        },
+        &out);
+    auto status = sync_completion_wait(&out.completion, zx::sec(kCodecTimeoutSecs).get());
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s failed to get gain format %d\n", __FUNCTION__, status);
+    }
+    *format = out.format;
+    return status;
+}
+
+zx_status_t Codec::GetGainState(gain_state_t* state) {
+    struct AsyncOut {
+        sync_completion_t completion;
+        gain_state_t state;
+    } out;
+    proto_client_.GetGainState(
+        [](void* ctx, const gain_state_t* state) {
+            auto* out = reinterpret_cast<AsyncOut*>(ctx);
+            out->state = *state;
+            sync_completion_signal(&out->completion);
+        },
+        &out);
+    auto status = sync_completion_wait(&out.completion, zx::sec(kCodecTimeoutSecs).get());
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s failed to get gain state %d\n", __FUNCTION__, status);
+    }
+    *state = out.state;
+    return status;
+}
+
+zx_status_t Codec::SetGainState(gain_state_t* state) {
+    proto_client_.SetGainState(state, [](void* ctx) {}, nullptr);
+    return ZX_OK;
+}
+
+} // namespace as370
+} // namespace audio
diff --git a/zircon/system/dev/audio/as370-tdm-output/codec.h b/zircon/system/dev/audio/as370-tdm-output/codec.h
new file mode 100644
index 0000000..520c0be
--- /dev/null
+++ b/zircon/system/dev/audio/as370-tdm-output/codec.h
@@ -0,0 +1,31 @@
+// Copyright 2019 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.
+
+#pragma once
+
+#include <ddk/debug.h>
+#include <ddktl/protocol/codec.h>
+#include <lib/sync/completion.h>
+#include <lib/zx/time.h>
+
+namespace audio {
+namespace as370 {
+
+struct Codec {
+    static constexpr uint32_t kCodecTimeoutSecs = 1;
+
+    struct AsyncOut {
+        sync_completion_t completion;
+        zx_status_t status;
+    };
+
+    zx_status_t GetGainFormat(gain_format_t* format);
+    zx_status_t GetGainState(gain_state_t* state);
+    zx_status_t SetGainState(gain_state_t* state);
+
+    ddk::CodecProtocolClient proto_client_;
+};
+
+} // namespace as370
+} // namespace audio
diff --git a/zircon/system/dev/audio/codecs/BUILD.gn b/zircon/system/dev/audio/codecs/BUILD.gn
index d3c381a..efbfda3 100644
--- a/zircon/system/dev/audio/codecs/BUILD.gn
+++ b/zircon/system/dev/audio/codecs/BUILD.gn
@@ -4,6 +4,7 @@
 
 group("codecs") {
   deps = [
+    "max98373",
     "tas5782",
     "tas5805",
   ]
diff --git a/zircon/system/dev/audio/codecs/max98373/BUILD.gn b/zircon/system/dev/audio/codecs/max98373/BUILD.gn
new file mode 100644
index 0000000..c1e575a
--- /dev/null
+++ b/zircon/system/dev/audio/codecs/max98373/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2019 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.
+
+driver("max98373") {
+  sources = [
+    "max98373.cpp",
+  ]
+  deps = [
+    "$zx/system/banjo/ddk.protocol.codec",
+    "$zx/system/banjo/ddk.protocol.composite",
+    "$zx/system/banjo/ddk.protocol.i2c",
+    "$zx/system/banjo/ddk.protocol.platform.device",
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/ddktl",
+    "$zx/system/ulib/fbl",
+    "$zx/system/ulib/zircon",
+    "$zx/system/ulib/zx",
+  ]
+}
+
+test("max98373-test") {
+  output_name = "max98373-test"
+  sources = [
+    "max98373-test.cpp",
+    "max98373.cpp",
+  ]
+  deps = [
+    "$zx/system/banjo/ddk.protocol.codec",
+    "$zx/system/banjo/ddk.protocol.composite",
+    "$zx/system/dev/lib/fake_ddk",
+    "$zx/system/dev/lib/mock-i2c",
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/ddktl",
+    "$zx/system/ulib/driver",
+    "$zx/system/ulib/fbl",
+    "$zx/system/ulib/sync",
+    "$zx/system/ulib/zircon",
+    "$zx/system/ulib/zx",
+    "$zx/system/ulib/zxtest",
+  ]
+}
diff --git a/zircon/system/dev/audio/codecs/max98373/max98373-test.cpp b/zircon/system/dev/audio/codecs/max98373/max98373-test.cpp
new file mode 100644
index 0000000..9b14ee3
--- /dev/null
+++ b/zircon/system/dev/audio/codecs/max98373/max98373-test.cpp
@@ -0,0 +1,199 @@
+// Copyright 2019 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 "max98373.h"
+
+#include <lib/mock-i2c/mock-i2c.h>
+#include <lib/sync/completion.h>
+#include <zxtest/zxtest.h>
+
+namespace audio {
+
+static constexpr uint32_t kCodecTimeoutSecs = 1;
+
+struct Max98373Test : public Max98373 {
+    explicit Max98373Test(zx_device_t* device, const ddk::I2cChannel& i2c)
+        : Max98373(device, i2c) {
+        initialized_ = true;
+    }
+    zx_status_t CodecSetDaiFormat(dai_format_t* format) {
+        struct AsyncOut {
+            sync_completion_t completion;
+            zx_status_t status;
+        } out;
+        Max98373::CodecSetDaiFormat(
+            format, [](void* ctx, zx_status_t s) {
+                AsyncOut* out = reinterpret_cast<AsyncOut*>(ctx);
+                out->status = s;
+                sync_completion_signal(&out->completion);
+            },
+            &out);
+        auto status = sync_completion_wait(&out.completion, zx::sec(kCodecTimeoutSecs).get());
+        if (status != ZX_OK) {
+            return status;
+        }
+        return out.status;
+    }
+};
+
+TEST(Max98373Test, GoodSetDai) {
+    mock_i2c::MockI2c mock_i2c;
+    ddk::I2cChannel i2c(mock_i2c.GetProto());
+    Max98373Test device(nullptr, std::move(i2c));
+
+    uint32_t channels[] = {0, 1};
+    dai_format_t format = {};
+    format.number_of_channels = 2;
+    format.channels_to_use_list = channels;
+    format.channels_to_use_count = countof(channels);
+    format.sample_format = SAMPLE_FORMAT_PCM_SIGNED;
+    format.justify_format = JUSTIFY_FORMAT_JUSTIFY_I2S;
+    format.frame_rate = 48000;
+    format.bits_per_channel = 32;
+
+    format.bits_per_sample = 32;
+    mock_i2c.ExpectWriteStop({0x33, 0x03}); // 32 bits.
+    EXPECT_OK(device.CodecSetDaiFormat(&format));
+
+    mock_i2c.ExpectWriteStop({0x33, 0x00}); // 16 bits.
+    format.bits_per_sample = 16;
+    EXPECT_OK(device.CodecSetDaiFormat(&format));
+
+    mock_i2c.VerifyAndClear();
+}
+
+TEST(Max98373Test, BadSetDai) {
+    mock_i2c::MockI2c mock_i2c;
+    ddk::I2cChannel i2c(mock_i2c.GetProto());
+    Max98373Test device(nullptr, std::move(i2c));
+
+    // No format at all.
+    EXPECT_EQ(ZX_ERR_INVALID_ARGS, device.CodecSetDaiFormat(nullptr));
+
+    // Blank format.
+    dai_format format = {};
+    EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, device.CodecSetDaiFormat(&format));
+
+    // Almost good format (wrong justify_format).
+    uint32_t channels[] = {0, 1};
+    format.number_of_channels = 2;
+    format.channels_to_use_list = channels;
+    format.channels_to_use_count = countof(channels);
+    format.sample_format = SAMPLE_FORMAT_PCM_SIGNED;
+    format.justify_format = JUSTIFY_FORMAT_JUSTIFY_LEFT; // This must fail, only I2S supported.
+    format.frame_rate = 48000;
+    format.bits_per_channel = 32;
+    format.bits_per_sample = 32;
+    EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, device.CodecSetDaiFormat(&format));
+
+    // Almost good format (wrong channels).
+    format.justify_format = JUSTIFY_FORMAT_JUSTIFY_I2S; // Restore I2S justify format.
+    format.channels_to_use_count = 1;
+    EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, device.CodecSetDaiFormat(&format));
+
+    // Almost good format (wrong rate).
+    format.channels_to_use_count = 2; // Restore channel count;
+    format.frame_rate = 1234;
+    EXPECT_EQ(ZX_ERR_NOT_SUPPORTED, device.CodecSetDaiFormat(&format));
+
+    mock_i2c.VerifyAndClear();
+}
+
+TEST(Max98373Test, GetDai) {
+    mock_i2c::MockI2c mock_i2c;
+    ddk::I2cChannel i2c(mock_i2c.GetProto());
+    Max98373 device(nullptr, std::move(i2c));
+    struct AsyncOut {
+        sync_completion_t completion;
+        zx_status_t status;
+    } out;
+
+    device.CodecGetDaiFormats(
+        [](void* ctx, zx_status_t status, const dai_supported_formats_t* formats_list,
+           size_t formats_count) {
+            AsyncOut* out = reinterpret_cast<AsyncOut*>(ctx);
+            EXPECT_EQ(formats_count, 1);
+            EXPECT_EQ(formats_list[0].number_of_channels_count, 1);
+            EXPECT_EQ(formats_list[0].number_of_channels_list[0], 2);
+            EXPECT_EQ(formats_list[0].sample_formats_count, 1);
+            EXPECT_EQ(formats_list[0].sample_formats_list[0], SAMPLE_FORMAT_PCM_SIGNED);
+            EXPECT_EQ(formats_list[0].justify_formats_count, 1);
+            EXPECT_EQ(formats_list[0].justify_formats_list[0], JUSTIFY_FORMAT_JUSTIFY_I2S);
+            EXPECT_EQ(formats_list[0].frame_rates_count, 1);
+            EXPECT_EQ(formats_list[0].frame_rates_list[0], 48000);
+            EXPECT_EQ(formats_list[0].bits_per_channel_count, 2);
+            EXPECT_EQ(formats_list[0].bits_per_channel_list[0], 16);
+            EXPECT_EQ(formats_list[0].bits_per_channel_list[1], 32);
+            EXPECT_EQ(formats_list[0].bits_per_sample_count, 2);
+            EXPECT_EQ(formats_list[0].bits_per_sample_list[0], 16);
+            EXPECT_EQ(formats_list[0].bits_per_sample_list[1], 32);
+            out->status = status;
+            sync_completion_signal(&out->completion);
+        },
+        &out);
+    EXPECT_OK(out.status);
+    EXPECT_OK(sync_completion_wait(&out.completion, zx::sec(kCodecTimeoutSecs).get()));
+}
+
+TEST(Max98373Test, GetInfo) {
+    ddk::I2cChannel unused_i2c;
+    Max98373 device(nullptr, std::move(unused_i2c));
+
+    device.CodecGetInfo(
+        [](void* ctx, const info_t* info) {
+            EXPECT_EQ(strcmp(info->unique_id, ""), 0);
+            EXPECT_EQ(strcmp(info->manufacturer, "Texas Instruments"), 0);
+            EXPECT_EQ(strcmp(info->product_name, "MAX98373m"), 0);
+        }, nullptr);
+}
+
+TEST(Max98373Test, BridgedMode) {
+    ddk::I2cChannel unused_i2c;
+    Max98373 device(nullptr, std::move(unused_i2c));
+
+    device.CodecIsBridgeable(
+        [](void* ctx, bool supports_bridged_mode) {
+            EXPECT_EQ(supports_bridged_mode, false);
+        }, nullptr);
+}
+
+TEST(Max98373Test, GetGainFormat) {
+    ddk::I2cChannel unused_i2c;
+    Max98373 device(nullptr, std::move(unused_i2c));
+
+    device.CodecGetGainFormat(
+        [](void* ctx, const gain_format_t* format) {
+            EXPECT_EQ(format->type, GAIN_TYPE_DECIBELS);
+            EXPECT_EQ(format->min_gain, -103.0);
+            EXPECT_EQ(format->max_gain, 24.0);
+            EXPECT_EQ(format->gain_step, 0.5);
+        }, nullptr);
+}
+
+TEST(Max98373Test, GetPlugState) {
+    ddk::I2cChannel unused_i2c;
+    Max98373 device(nullptr, std::move(unused_i2c));
+
+    device.CodecGetPlugState(
+        [](void* ctx, const plug_state_t* state) {
+            EXPECT_EQ(state->hardwired, true);
+            EXPECT_EQ(state->plugged, true);
+        }, nullptr);
+}
+
+TEST(Max98373Test, Reset) {
+    mock_i2c::MockI2c mock_i2c;
+    mock_i2c
+        .ExpectWriteStop({0x00, 0x00})  // Page 0.
+        .ExpectWriteStop({0x03, 0x00})  // Enter standby.
+        .ExpectWriteStop({0x01, 0x11})  // Reset registers and modules.
+        .ExpectWriteStop({0x02, 0x05})  // Normal modulation, stereo.
+        .ExpectWriteStop({0x03, 0x03}); // Exit standby.
+
+    ddk::I2cChannel i2c(mock_i2c.GetProto());
+    Max98373 device(nullptr, std::move(i2c));
+    device.ResetAndInitialize();
+    mock_i2c.VerifyAndClear();
+}
+} // namespace audio
diff --git a/zircon/system/dev/audio/codecs/max98373/max98373.cpp b/zircon/system/dev/audio/codecs/max98373/max98373.cpp
new file mode 100644
index 0000000..633700f
--- /dev/null
+++ b/zircon/system/dev/audio/codecs/max98373/max98373.cpp
@@ -0,0 +1,310 @@
+// Copyright 2019 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 "max98373.h"
+
+#include <algorithm>
+#include <memory>
+
+#include <ddk/binding.h>
+#include <ddk/platform-defs.h>
+#include <ddk/protocol/composite.h>
+#include <ddk/protocol/i2c-lib.h>
+#include <ddk/protocol/i2c.h>
+#include <fbl/algorithm.h>
+#include <fbl/alloc_checker.h>
+
+namespace {
+static constexpr uint8_t SW_RESET = 0x01;       //sw reset
+static constexpr uint8_t PWR_CTL = 0x02;        //power control
+static constexpr uint8_t PB_CFG2 = 0x05;        //pcm gain register
+static constexpr uint8_t TDM_CFG0 = 0x0a;
+static constexpr uint8_t TDM_CFG1 = 0x0b;
+static constexpr uint8_t TDM_CFG2 = 0x0c;
+static constexpr uint8_t TDM_CFG3 = 0x0d;
+static constexpr uint8_t TDM_CFG4 = 0x0e;
+static constexpr uint8_t TDM_CFG5 = 0x0f;
+static constexpr uint8_t TDM_CFG6 = 0x10;
+static constexpr uint8_t TDM_CFG7 = 0x11;
+static constexpr uint8_t TDM_CFG8 = 0x12;
+static constexpr uint8_t TDM_CFG9 = 0x13;
+static constexpr uint8_t TDM_CFG10 = 0x14;
+static constexpr uint8_t CLOCK_CFG = 0x3c;      //Clock Config
+
+// TODO(andresoportus): Add handling for the other formats supported by this codec.
+static const uint32_t supported_n_channels[] = {2};
+static const sample_format_t supported_sample_formats[] = {SAMPLE_FORMAT_PCM_SIGNED};
+static const justify_format_t supported_justify_formats[] = {JUSTIFY_FORMAT_JUSTIFY_I2S};
+static const uint32_t supported_rates[] = {48000};
+static const uint8_t supported_bits_per_channel[] = {32};
+static const uint8_t supported_bits_per_sample[] = {32};
+static const dai_supported_formats_t kSupportedDaiFormats = {
+    .number_of_channels_list = supported_n_channels,
+    .number_of_channels_count = countof(supported_n_channels),
+    .sample_formats_list = supported_sample_formats,
+    .sample_formats_count = countof(supported_sample_formats),
+    .justify_formats_list = supported_justify_formats,
+    .justify_formats_count = countof(supported_justify_formats),
+    .frame_rates_list = supported_rates,
+    .frame_rates_count = countof(supported_rates),
+    .bits_per_channel_list = supported_bits_per_channel,
+    .bits_per_channel_count = countof(supported_bits_per_channel),
+    .bits_per_sample_list = supported_bits_per_sample,
+    .bits_per_sample_count = countof(supported_bits_per_sample),
+};
+
+enum {
+    COMPONENT_I2C,
+    COMPONENT_RESET_GPIO,
+    COMPONENT_COUNT,
+};
+
+} // namespace
+
+namespace audio {
+
+zx_status_t Max98373::ResetAndInitialize() {
+    uint16_t buffer;
+    uint8_t address[2] = {0x21, 0xff};
+    auto status = i2c_.WriteReadSync((uint8_t*)address, 2, (uint8_t*)(&buffer), 1);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s Failed to read I2C register\n", __FILE__);
+        return status;
+    }
+    constexpr uint8_t defaults[][2] = {
+    };
+    for (auto& i : defaults) {
+        auto status = WriteReg(i[0], i[1]);
+        if (status != ZX_OK) {
+            zxlogf(ERROR, "%s Failed to write I2C register 0x%02X\n", __FILE__, i[0]);
+            return status;
+        }
+    }
+    initialized_ = true;
+    zxlogf(INFO, "%s REV ID: %X\n", __FILE__, buffer);
+    return ZX_OK;
+}
+
+zx_status_t Max98373::Bind() {
+    auto thunk =
+        [](void* arg) -> int { return reinterpret_cast<Max98373*>(arg)->ResetAndInitialize(); };
+    int rc = thrd_create_with_name(&thread_, thunk, this, "Max98373-thread");
+    if (rc != thrd_success) {
+        return ZX_ERR_INTERNAL;
+    }
+    zx_device_prop_t props[] = {
+        {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_MAXIM},
+        {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_MAXIM_MAX98373},
+    };
+    return DdkAdd("max98373", 0, props, countof(props));
+}
+
+void Max98373::Shutdown() {
+    thrd_join(thread_, NULL);
+}
+
+zx_status_t Max98373::Create(zx_device_t* parent) {
+    composite_protocol_t composite;
+
+    auto status = device_get_protocol(parent, ZX_PROTOCOL_COMPOSITE, &composite);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s Could not get composite protocol\n", __FILE__);
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+
+    zx_device_t* components[COMPONENT_COUNT] = {};
+    size_t actual = 0;
+    composite_get_components(&composite, components, countof(components), &actual);
+    if (actual != COMPONENT_COUNT) {
+        zxlogf(ERROR, "%s Could not get components\n", __FILE__);
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+
+    fbl::AllocChecker ac;
+    auto dev = std::unique_ptr<Max98373>(new (&ac) Max98373(parent, components[COMPONENT_I2C],
+                                            components[COMPONENT_RESET_GPIO]));
+    status = dev->Bind();
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    // devmgr is now in charge of the memory for dev.
+    dev.release();
+    return ZX_OK;
+}
+
+void Max98373::CodecReset(codec_reset_callback callback, void* cookie) {
+    if (!initialized_) {
+        callback(cookie, ZX_ERR_UNAVAILABLE);
+        return;
+    }
+    auto status = ResetAndInitialize();
+    callback(cookie, status);
+}
+
+void Max98373::CodecGetInfo(codec_get_info_callback callback, void* cookie) {
+    info_t info;
+    info.unique_id = "";
+    info.manufacturer = "Texas Instruments";
+    info.product_name = "MAX98373";
+    callback(cookie, &info);
+}
+
+void Max98373::CodecIsBridgeable(codec_is_bridgeable_callback callback, void* cookie) {
+    callback(cookie, false);
+}
+
+void Max98373::CodecSetBridgedMode(bool enable_bridged_mode,
+                                  codec_set_bridged_mode_callback callback, void* cookie) {
+    // TODO(andresoportus): Add support and report true in CodecIsBridgeable.
+    callback(cookie);
+}
+
+void Max98373::CodecGetDaiFormats(codec_get_dai_formats_callback callback, void* cookie) {
+    callback(cookie, ZX_OK, &kSupportedDaiFormats, 1);
+}
+
+void Max98373::CodecSetDaiFormat(const dai_format_t* format, codec_set_dai_format_callback callback,
+                                void* cookie) {
+    if (!initialized_) {
+        callback(cookie, ZX_ERR_UNAVAILABLE);
+        return;
+    }
+    if (format == nullptr) {
+        callback(cookie, ZX_ERR_INVALID_ARGS);
+        return;
+    }
+
+    // Only allow 2 channels.
+    if (format->number_of_channels != 2) {
+        zxlogf(ERROR, "%s DAI format number of channels not supported\n", __FILE__);
+        callback(cookie, ZX_ERR_NOT_SUPPORTED);
+        return;
+    }
+    if (format->channels_to_use_count != 2 ||
+        format->channels_to_use_list == nullptr ||
+        format->channels_to_use_list[0] != 0 ||
+        format->channels_to_use_list[1] != 1) {
+        zxlogf(ERROR, "%s DAI format channels to use not supported\n", __FILE__);
+        callback(cookie, ZX_ERR_NOT_SUPPORTED);
+        return;
+    }
+
+    // Only I2S.
+    if (format->sample_format != SAMPLE_FORMAT_PCM_SIGNED ||
+        format->justify_format != JUSTIFY_FORMAT_JUSTIFY_I2S) {
+        zxlogf(ERROR, "%s DAI format format not supported\n", __FILE__);
+        callback(cookie, ZX_ERR_NOT_SUPPORTED);
+        return;
+    }
+
+    // Check rates allowed.
+    size_t i = 0;
+    for (i = 0; i < kSupportedDaiFormats.frame_rates_count; ++i) {
+        if (format->frame_rate == kSupportedDaiFormats.frame_rates_list[i]) {
+            break;
+        }
+    }
+    if (i == kSupportedDaiFormats.frame_rates_count) {
+        zxlogf(ERROR, "%s DAI format rates not supported\n", __FILE__);
+        callback(cookie, ZX_ERR_NOT_SUPPORTED);
+        return;
+    }
+
+    // Allow bits per sample/channel of 16/16, 16/32 or 32/32 bits.
+    if (!(format->bits_per_sample == 32 && format->bits_per_channel == 32)) {
+        zxlogf(ERROR, "%s DAI format number of bits not supported\n", __FILE__);
+        callback(cookie, ZX_ERR_NOT_SUPPORTED);
+        return;
+    }
+    callback(cookie, ZX_OK);
+}
+
+void Max98373::CodecGetGainFormat(codec_get_gain_format_callback callback, void* cookie) {
+    gain_format_t format = {};
+    format.type = GAIN_TYPE_DECIBELS;
+    format.min_gain = kMinGain;
+    format.max_gain = kMaxGain;
+    format.gain_step = kGainStep;
+    callback(cookie, &format);
+}
+
+void Max98373::CodecSetGainState(const gain_state_t* gain_state,
+                                codec_set_gain_state_callback callback, void* cookie) {
+    if (!initialized_) {
+        zxlogf(ERROR, "%s Couldn't set gain, not initialized yet\n", __FILE__);
+        callback(cookie);
+    }
+    float gain = std::clamp(gain_state->gain, kMinGain, kMaxGain);
+    uint8_t gain_reg = static_cast<uint8_t>(-gain / kGainStep);
+    zx_status_t status = WriteReg(PB_CFG2, gain_reg);
+    if (status != ZX_OK) {
+        callback(cookie);
+        return;
+    }
+    current_gain_ = gain;
+    callback(cookie);
+}
+
+void Max98373::CodecGetGainState(codec_get_gain_state_callback callback, void* cookie) {
+    gain_state_t gain_state = {};
+    gain_state.gain = current_gain_;
+    gain_state.muted = false;
+    gain_state.agc_enable = false;
+    callback(cookie, &gain_state);
+}
+
+void Max98373::CodecGetPlugState(codec_get_plug_state_callback callback, void* cookie) {
+    plug_state_t plug_state = {};
+    plug_state.hardwired = true;
+    plug_state.plugged = true;
+    callback(cookie, &plug_state);
+}
+
+zx_status_t Max98373::WriteReg(uint8_t reg, uint8_t value) {
+    uint8_t write_buf[2];
+    write_buf[0] = reg;
+    write_buf[1] = value;
+#define TRACE_I2C
+#ifdef TRACE_I2C
+    printf("Writing register 0x%02X to value 0x%02X\n", reg, value);
+    auto status = i2c_.WriteSync(write_buf, 2);
+    if (status != ZX_OK) {
+        printf("Could not I2C write %d\n", status);
+        return status;
+    }
+    uint8_t buffer = 0;
+    i2c_.ReadSync(reg, &buffer, 1);
+    if (status != ZX_OK) {
+        printf("Could not I2C read %d\n", status);
+        return status;
+    }
+    printf("Read register just written 0x%02X, value 0x%02X\n", reg, buffer);
+    return ZX_OK;
+#else
+    return i2c_.WriteSync(write_buf, 2);
+#endif
+}
+
+zx_status_t max98373_bind(void* ctx, zx_device_t* parent) {
+    return Max98373::Create(parent);
+}
+
+static zx_driver_ops_t driver_ops = []() {
+    zx_driver_ops_t ops;
+    ops.version = DRIVER_OPS_VERSION;
+    ops.bind = max98373_bind;
+    return ops;
+}();
+
+} // namespace audio
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(ti_max98373, audio::driver_ops, "zircon", "0.1", 3)
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MAXIM),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_MAXIM_MAX98373),
+ZIRCON_DRIVER_END(ti_max98373)
+// clang-format on
+
diff --git a/zircon/system/dev/audio/codecs/max98373/max98373.h b/zircon/system/dev/audio/codecs/max98373/max98373.h
new file mode 100644
index 0000000..8fa9b74
--- /dev/null
+++ b/zircon/system/dev/audio/codecs/max98373/max98373.h
@@ -0,0 +1,77 @@
+// Copyright 2019 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.
+
+#pragma once
+
+#include <threads.h>
+
+#include <memory>
+
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddktl/device.h>
+#include <ddktl/i2c-channel.h>
+#include <ddktl/protocol/codec.h>
+#include <ddktl/protocol/gpio.h>
+
+namespace audio {
+
+class Max98373;
+using DeviceType = ddk::Device<Max98373, ddk::Unbindable>;
+
+class Max98373 : public DeviceType, // Not final for unit tests.
+                public ddk::CodecProtocol<Max98373, ddk::base_protocol> {
+public:
+    static zx_status_t Create(zx_device_t* parent);
+
+    explicit Max98373(zx_device_t* device, const ddk::I2cChannel& i2c,
+                     const ddk::GpioProtocolClient& codec_reset)
+        : DeviceType(device), i2c_(i2c), codec_reset_(codec_reset) {}
+    zx_status_t Bind();
+
+    void DdkRelease() {
+        delete this;
+    }
+    void DdkUnbind() {
+        Shutdown();
+        DdkRemove();
+    }
+    zx_status_t DdkSuspend(uint32_t flags) {
+        Shutdown();
+        return ZX_OK;
+    }
+
+    void CodecReset(codec_reset_callback callback, void* cookie);
+    void CodecGetInfo(codec_get_info_callback callback, void* cookie);
+    void CodecIsBridgeable(codec_is_bridgeable_callback callback, void* cookie);
+    void CodecSetBridgedMode(bool enable_bridged_mode, codec_set_bridged_mode_callback callback,
+                             void* cookie);
+    void CodecGetDaiFormats(codec_get_dai_formats_callback callback, void* cookie);
+    void CodecSetDaiFormat(const dai_format_t* format, codec_set_dai_format_callback callback,
+                           void* cookie);
+    void CodecGetGainFormat(codec_get_gain_format_callback callback, void* cookie);
+    void CodecGetGainState(codec_get_gain_state_callback callback, void* cookie);
+    void CodecSetGainState(const gain_state_t* gain_state, codec_set_gain_state_callback callback,
+                           void* cookie);
+    void CodecGetPlugState(codec_get_plug_state_callback callback, void* cookie);
+
+    zx_status_t ResetAndInitialize();
+
+protected:
+    bool initialized_ = false; // Protected for unit tests.
+
+private:
+    static constexpr float kMaxGain = 0;
+    static constexpr float kMinGain = -100.0;
+    static constexpr float kGainStep = 0.5;
+
+    zx_status_t WriteReg(uint8_t reg, uint8_t value);
+    void Shutdown();
+
+    ddk::I2cChannel i2c_;
+    ddk::GpioProtocolClient codec_reset_;
+    float current_gain_ = 0;
+    thrd_t thread_;
+};
+} // namespace audio
diff --git a/zircon/system/dev/board/as370/BUILD.gn b/zircon/system/dev/board/as370/BUILD.gn
index bb87bfe..a5147b5 100644
--- a/zircon/system/dev/board/as370/BUILD.gn
+++ b/zircon/system/dev/board/as370/BUILD.gn
@@ -8,6 +8,7 @@
     "as370-gpio.cpp",
     "as370-i2c.cpp",
     "as370-usb.cpp",
+    "as370-audio.cpp",
   ]
   deps = [
     "$zx/system/banjo/ddk.protocol.gpioimpl",
diff --git a/zircon/system/dev/board/as370/as370-audio.cpp b/zircon/system/dev/board/as370/as370-audio.cpp
new file mode 100644
index 0000000..6d5493e
--- /dev/null
+++ b/zircon/system/dev/board/as370/as370-audio.cpp
@@ -0,0 +1,103 @@
+// Copyright 2019 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 <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/metadata.h>
+#include <ddk/platform-defs.h>
+#include <ddk/protocol/gpio.h>
+#include <ddktl/metadata/audio.h>
+#include <fbl/algorithm.h>
+#include <limits.h>
+#include <soc/as370/as370-gpio.h>
+#include <soc/as370/as370-i2c.h>
+
+#include "as370.h"
+
+namespace board_as370 {
+
+constexpr zx_bind_inst_t root_match[] = {
+    BI_MATCH(),
+};
+static const zx_bind_inst_t ref_out_i2c_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
+    BI_ABORT_IF(NE, BIND_I2C_BUS_ID, 0),
+    BI_MATCH_IF(EQ, BIND_I2C_ADDRESS, 0x31),
+};
+static const zx_bind_inst_t ref_out_codec_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_CODEC),
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MAXIM),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_MAXIM_MAX98373),
+};
+static const device_component_part_t ref_out_i2c_component[] = {
+    {fbl::count_of(root_match), root_match},
+    {fbl::count_of(ref_out_i2c_match), ref_out_i2c_match},
+};
+static const device_component_part_t ref_out_codec_component[] = {
+    {fbl::count_of(root_match), root_match},
+    {fbl::count_of(ref_out_codec_match), ref_out_codec_match},
+};
+
+static const zx_bind_inst_t ref_out_enable_gpio_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_GPIO),
+    BI_MATCH_IF(EQ, BIND_GPIO_PIN, 17),
+};
+static const device_component_part_t ref_out_enable_gpio_component[] = {
+    {countof(root_match), root_match},
+    {countof(ref_out_enable_gpio_match), ref_out_enable_gpio_match},
+};
+
+static const device_component_t codec_components[] = {
+    {countof(ref_out_i2c_component), ref_out_i2c_component},
+    {countof(ref_out_enable_gpio_component), ref_out_enable_gpio_component},
+};
+static const device_component_t controller_components[] = {
+    {countof(ref_out_codec_component), ref_out_codec_component},
+};
+
+zx_status_t As370::AudioInit() {
+
+    pbus_dev_t controller_out = {};
+    controller_out.name = "as370-audio-out";
+    controller_out.vid = PDEV_VID_SYNAPTICS;
+    controller_out.pid = PDEV_PID_SYNAPTICS_AS370;
+    controller_out.did = PDEV_DID_SYNAPTICS_AUDIO_OUT;
+
+    // Output pin assignments.
+    // AMP_EN.
+    gpio_impl_.SetAltFunction(17, 0); // mode 0 to set as GPIO.
+    gpio_impl_.ConfigOut(17, 0);
+    zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
+    gpio_impl_.ConfigOut(17, 1);
+    zx_nanosleep(zx_deadline_after(ZX_MSEC(3)));
+
+    gpio_impl_.SetAltFunction(6, 1); // mode 1 to set as I2S1_MCLK.
+    gpio_impl_.SetAltFunction(0, 1); // mode 1 to set as I2S1_BCLKIO (TDM_BCLK).
+    gpio_impl_.SetAltFunction(1, 1); // mode 1 to set as I2S1_LRLKIO (TDM_FSYNC).
+    gpio_impl_.SetAltFunction(2, 1); // mode 1 to set as I2S1_DO[0] (TDM_MOSI).
+
+    // Output devices.
+    constexpr zx_device_prop_t props[] = {
+        {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_MAXIM},
+        {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_MAXIM_MAX98373}
+    };
+    auto status = DdkAddComposite("audio-tas5772", props, countof(props), codec_components,
+                                  countof(codec_components), UINT32_MAX);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: DdkAddComposite failed %d\n", __FUNCTION__, status);
+        return status;
+    }
+
+    status = pbus_.CompositeDeviceAdd(&controller_out, controller_components,
+                                      countof(controller_components), UINT32_MAX);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "%s: CompositeDeviceAdd failed %d\n", __FUNCTION__, status);
+        return status;
+    }
+
+    return ZX_OK;
+}
+
+} // namespace board_mt8167
diff --git a/zircon/system/dev/board/as370/as370-gpio.cpp b/zircon/system/dev/board/as370/as370-gpio.cpp
index a753aa7..964dcf0 100644
--- a/zircon/system/dev/board/as370/as370-gpio.cpp
+++ b/zircon/system/dev/board/as370/as370-gpio.cpp
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include <ddk/debug.h>
+#include <ddk/metadata.h>
+#include <ddk/metadata/gpio.h>
 #include <ddk/platform-defs.h>
 #include <soc/as370/as370-gpio.h>
 
@@ -26,6 +28,18 @@
         },
     };
 
+    const gpio_pin_t gpio_pins[] = {
+        { 17 }, // AMP_EN.
+    };
+
+    const pbus_metadata_t gpio_metadata[] = {
+        {
+            .type = DEVICE_METADATA_GPIO_PINS,
+            .data_buffer = &gpio_pins,
+            .data_size = sizeof(gpio_pins),
+        }
+    };
+
     pbus_dev_t gpio_dev = {};
     gpio_dev.name = "gpio";
     gpio_dev.vid = PDEV_VID_SYNAPTICS;
@@ -33,13 +47,19 @@
     gpio_dev.did = PDEV_DID_SYNAPTICS_GPIO;
     gpio_dev.mmio_list = gpio_mmios;
     gpio_dev.mmio_count = countof(gpio_mmios);
+    gpio_dev.metadata_list = gpio_metadata;
+    gpio_dev.metadata_count = countof(gpio_metadata);
 
-    zx_status_t status = pbus_.ProtocolDeviceAdd(ZX_PROTOCOL_GPIO_IMPL, &gpio_dev);
+    auto status = pbus_.ProtocolDeviceAdd(ZX_PROTOCOL_GPIO_IMPL, &gpio_dev);
     if (status != ZX_OK) {
         zxlogf(ERROR, "%s: ProtocolDeviceAdd failed: %d\n", __func__, status);
         return status;
     }
-
+    gpio_impl_ = ddk::GpioImplProtocolClient(parent());
+    if (!gpio_impl_.is_valid()) {
+        zxlogf(ERROR, "%s: device_get_protocol failed %d\n", __func__, status);
+        return ZX_ERR_INTERNAL;
+    }
     return ZX_OK;
 }
 
diff --git a/zircon/system/dev/board/as370/as370-i2c.cpp b/zircon/system/dev/board/as370/as370-i2c.cpp
index 23f194a..bd08ed8 100644
--- a/zircon/system/dev/board/as370/as370-i2c.cpp
+++ b/zircon/system/dev/board/as370/as370-i2c.cpp
@@ -64,7 +64,16 @@
         },
     };
 
-    constexpr i2c_channel_t i2c_channels[] = {};
+    constexpr i2c_channel_t i2c_channels[] = {
+        // For audio out
+        {
+            .bus_id = 0,
+            .address = 0x31,
+            .vid = 0,
+            .pid = 0,
+            .did = 0,
+        },
+    };
 
     const pbus_metadata_t i2c_metadata[] = {
         {
diff --git a/zircon/system/dev/board/as370/as370.cpp b/zircon/system/dev/board/as370/as370.cpp
index 26114a3..8bd586b 100644
--- a/zircon/system/dev/board/as370/as370.cpp
+++ b/zircon/system/dev/board/as370/as370.cpp
@@ -62,6 +62,10 @@
     if (UsbInit() != ZX_OK) {
         zxlogf(ERROR, "%s: UsbInit() failed\n", __func__);
     }
+    if (AudioInit() != ZX_OK) {
+        zxlogf(ERROR, "%s: AudioInit() failed\n", __func__);
+        // In case of error report it and keep going.
+    }
 
     return 0;
 }
diff --git a/zircon/system/dev/board/as370/as370.h b/zircon/system/dev/board/as370/as370.h
index 4ca51c4..033956c 100644
--- a/zircon/system/dev/board/as370/as370.h
+++ b/zircon/system/dev/board/as370/as370.h
@@ -7,6 +7,7 @@
 #include <threads.h>
 
 #include <ddktl/device.h>
+#include <ddktl/protocol/gpioimpl.h>
 #include <ddktl/protocol/platform/bus.h>
 
 namespace board_as370 {
@@ -33,8 +34,10 @@
     zx_status_t GpioInit();
     zx_status_t I2cInit();
     zx_status_t UsbInit();
+    zx_status_t AudioInit();
 
     ddk::PBusProtocolClient pbus_;
+    ddk::GpioImplProtocolClient gpio_impl_;
     thrd_t thread_;
 };
 
diff --git a/zircon/system/ulib/ddk/include/ddk/platform-defs.h b/zircon/system/ulib/ddk/include/ddk/platform-defs.h
index cdf7cb4..149a145 100644
--- a/zircon/system/ulib/ddk/include/ddk/platform-defs.h
+++ b/zircon/system/ulib/ddk/include/ddk/platform-defs.h
@@ -182,6 +182,7 @@
 #define PDEV_DID_TI_BACKLIGHT       1
 #define PDEV_DID_TI_TAS5805         2
 #define PDEV_DID_TI_TAS5782         3
+#define PDEV_DID_TI_TAS2770         4
 
 // Test
 #define PDEV_VID_TEST               17
@@ -231,6 +232,12 @@
 #define PDEV_PID_SYNAPTICS_AS370     1
 #define PDEV_DID_SYNAPTICS_GPIO      1
 #define PDEV_DID_AS370_USB_PHY       2
+#define PDEV_DID_SYNAPTICS_AUDIO_OUT 3
+#define PDEV_DID_SYNAPTICS_AUDIO_IN  4
+
+// Maxim
+#define PDEV_VID_MAXIM              21
+#define PDEV_DID_MAXIM_MAX98373      1
 
 __END_CDECLS
 
diff --git a/zircon/system/utest/BUILD.gn b/zircon/system/utest/BUILD.gn
index 3f811dc..ed365ae 100644
--- a/zircon/system/utest/BUILD.gn
+++ b/zircon/system/utest/BUILD.gn
@@ -26,6 +26,8 @@
       "$zx/system/core/netsvc:netsvc-test",
       "$zx/system/core/svchost:crashsvc-test",
       "$zx/system/core/virtcon:virtual-console-test",
+#      "$zx/system/dev/audio/codecs/max98373:max98373-test",
+#      "$zx/system/dev/audio/codecs/tas2770:tas2770-test",
       "$zx/system/dev/audio/codecs/tas5782:tas5782-test",
       "$zx/system/dev/audio/codecs/tas5805:tas5805-test",
       "$zx/system/dev/audio/lib/simple-audio-stream:sa-unittest",