[wip][nosubmit] Add examples folder

Change-Id: I8ae4d0c620b984322d296e9f813742e3897c7f38
diff --git a/src/devices/example/fidl-tables/aml-cpu.cc b/src/devices/example/fidl-tables/aml-cpu.cc
new file mode 100644
index 0000000..5b20ac8
--- /dev/null
+++ b/src/devices/example/fidl-tables/aml-cpu.cc
@@ -0,0 +1,402 @@
+// 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 "aml-cpu.h"
+
+#include <lib/ddk/debug.h>
+#include <lib/ddk/driver.h>
+#include <lib/ddk/platform-defs.h>
+#include <lib/device-protocol/pdev.h>
+#include <lib/inspect/cpp/inspector.h>
+#include <lib/mmio/mmio.h>
+
+#include <memory>
+#include <vector>
+
+#include <ddktl/fidl.h>
+#include <fbl/string_buffer.h>
+
+#include "src/devices/cpu/drivers/aml-cpu/aml-cpu-bind.h"
+
+namespace amlogic_cpu {
+
+namespace {
+// Fragments are provided to this driver in groups of 4. Fragments are provided as follows:
+// [4 fragments for cluster 0]
+// [4 fragments for cluster 1]
+// [...]
+// [4 fragments for cluster n]
+// Each fragment is a combination of the fixed string + id.
+constexpr size_t kFragmentsPerPfDomain = 4;
+
+constexpr zx_off_t kCpuVersionOffset = 0x220;
+
+}  // namespace
+
+zx_status_t AmlCpu::Create(void* context, zx_device_t* parent) {
+  zx_status_t st;
+  size_t actual;
+
+  // Get the metadata for the performance domains.
+  size_t perf_domain_size = 0;
+  st = device_get_metadata_size(parent, DEVICE_METADATA_AML_PERF_DOMAINS, &perf_domain_size);
+  zxlogf(DEBUG, "%s: Got AML_PERF_DOMAINS metadata size, st = %d, size = %lu", __func__, st,
+         perf_domain_size);
+
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to get performance domain count from board driver, st = %d", __func__,
+           st);
+    return st;
+  }
+
+  // Make sure that the board driver gave us an exact integer number of performance domains.
+  if (perf_domain_size % sizeof(perf_domain_t) != 0) {
+    zxlogf(ERROR,
+           "%s: Performance domain metadata from board driver is malformed. perf_domain_size = "
+           "%lu, sizeof(perf_domain_t) = %lu",
+           __func__, perf_domain_size, sizeof(perf_domain_t));
+    return ZX_ERR_INTERNAL;
+  }
+
+  const size_t num_perf_domains = perf_domain_size / sizeof(perf_domain_t);
+  std::unique_ptr<perf_domain_t[]> perf_domains =
+      std::make_unique<perf_domain_t[]>(num_perf_domains);
+  st = device_get_metadata(parent, DEVICE_METADATA_AML_PERF_DOMAINS, perf_domains.get(),
+                           perf_domain_size, &actual);
+  zxlogf(DEBUG, "%s: Got AML_PERF_DOMAINS metadata, st = %d, actual = %lu", __func__, st, actual);
+
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to get performance domain metadata from board driver, st = %d",
+           __func__, st);
+    return st;
+  }
+  if (actual != perf_domain_size) {
+    zxlogf(ERROR, "%s: Expected %lu bytes in perf domain metadata, got %lu", __func__,
+           perf_domain_size, actual);
+    return ZX_ERR_INTERNAL;
+  }
+
+  size_t op_point_size;
+  st = device_get_metadata_size(parent, DEVICE_METADATA_AML_OP_POINTS, &op_point_size);
+  zxlogf(DEBUG, "%s: Got AML_OP_POINTS metadata size, st = %d, size = %lu", __func__, st,
+         op_point_size);
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to get opp count metadata size from board driver, st = %d", __func__,
+           st);
+    return st;
+  }
+
+  if (op_point_size % sizeof(operating_point_t) != 0) {
+    zxlogf(ERROR, "%s: Operating point metadata from board driver is malformed", __func__);
+    return ZX_ERR_INTERNAL;
+  }
+
+  const size_t num_op_points = op_point_size / sizeof(operating_point_t);
+  std::unique_ptr<operating_point_t[]> operating_points =
+      std::make_unique<operating_point_t[]>(num_op_points);
+  st = device_get_metadata(parent, DEVICE_METADATA_AML_OP_POINTS, operating_points.get(),
+                           op_point_size, &actual);
+  zxlogf(DEBUG, "%s: Got AML_OP_POINTS metadata, st = %d, actual = %lu", __func__, st, actual);
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to get operating points from board driver, st = %d", __func__, st);
+    return st;
+  }
+  if (actual != op_point_size) {
+    zxlogf(ERROR, "%s: Expected %lu bytes in operating point metadata, got %lu", __func__,
+           op_point_size, actual);
+    return ZX_ERR_INTERNAL;
+  }
+  // Make sure we have the right number of fragments.
+  const uint32_t fragment_count = device_get_fragment_count(parent);
+  zxlogf(DEBUG, "%s: GetFragmentCount = %u", __func__, fragment_count);
+  if ((num_perf_domains * kFragmentsPerPfDomain) + 1 != fragment_count) {
+    zxlogf(ERROR,
+           "%s: Expected %lu fragments for each %lu performance domains for a total of %lu "
+           "fragments but got %u instead",
+           __func__, kFragmentsPerPfDomain, perf_domain_size,
+           perf_domain_size * kFragmentsPerPfDomain, fragment_count);
+    return ZX_ERR_INTERNAL;
+  }
+
+  // Map AOBUS registers
+  auto pdev = ddk::PDev::FromFragment(parent);
+  if (!pdev.is_valid()) {
+    zxlogf(ERROR, "Failed to get platform device fragment");
+    return ZX_ERR_NO_RESOURCES;
+  }
+  std::optional<ddk::MmioBuffer> mmio_buffer;
+  if ((st = pdev.MapMmio(0, &mmio_buffer)) != ZX_OK) {
+    zxlogf(ERROR, "aml-cpu: Failed to map mmio, st = %d", st);
+    return st;
+  }
+  const uint32_t cpu_version_packed = mmio_buffer->Read32(kCpuVersionOffset);
+
+  // Build and publish each performance domain.
+  for (size_t i = 0; i < num_perf_domains; i++) {
+    const perf_domain_t& perf_domain = perf_domains[i];
+
+    fbl::StringBuffer<32> fragment_name;
+    fragment_name.AppendPrintf("clock-pll-div16-%02d", perf_domain.id);
+    ddk::ClockProtocolClient pll_div16_client;
+    if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
+                                                         &pll_div16_client)) != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to create pll_div_16 clock client, st = %d", __func__, st);
+      return st;
+    }
+
+    fragment_name.Resize(0);
+    fragment_name.AppendPrintf("clock-cpu-div16-%02d", perf_domain.id);
+    ddk::ClockProtocolClient cpu_div16_client;
+    if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
+                                                         &cpu_div16_client)) != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to create cpu_div_16 clock client, st = %d", __func__, st);
+      return st;
+    }
+
+    fragment_name.Resize(0);
+    fragment_name.AppendPrintf("clock-cpu-scaler-%02d", perf_domain.id);
+    ddk::ClockProtocolClient cpu_scaler_client;
+    if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
+                                                         &cpu_scaler_client)) != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to create cpu_scaler clock client, st = %d", __func__, st);
+      return st;
+    }
+
+    fragment_name.Resize(0);
+    fragment_name.AppendPrintf("power-%02d", perf_domain.id);
+    ddk::PowerProtocolClient power_client;
+    if ((st = ddk::PowerProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
+                                                         &power_client)) != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to create power client, st = %d", __func__, st);
+      return st;
+    }
+
+    // Vector of operating points that belong to this power domain.
+    std::vector<operating_point_t> pd_op_points;
+    std::copy_if(operating_points.get(), operating_points.get() + num_op_points,
+                 std::back_inserter(pd_op_points), [&perf_domain](const operating_point_t& op) {
+                   return op.pd_id == perf_domain.id;
+                 });
+
+    // Order operating points from highest frequency to lowest because Operating Point 0 is the
+    // fastest.
+    std::sort(pd_op_points.begin(), pd_op_points.end(),
+              [](const operating_point_t& a, const operating_point_t& b) {
+                return a.freq_hz > b.freq_hz;
+              });
+
+    const size_t perf_state_count = pd_op_points.size();
+    auto perf_states = std::make_unique<device_performance_state_info_t[]>(perf_state_count);
+    for (size_t j = 0; j < perf_state_count; j++) {
+      perf_states[j].state_id = static_cast<uint8_t>(j);
+      perf_states[j].restore_latency = 0;
+    }
+
+    auto device = std::make_unique<AmlCpu>(
+        parent, std::move(pll_div16_client), std::move(cpu_div16_client),
+        std::move(cpu_scaler_client), std::move(power_client), std::move(pd_op_points),
+        perf_domain.core_count);
+
+    st = device->Init();
+    if (st != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to initialize device, st = %d", __func__, st);
+      return st;
+    }
+
+    device->SetCpuInfo(cpu_version_packed);
+
+    st = device->DdkAdd(ddk::DeviceAddArgs("cpu")
+                            .set_flags(DEVICE_ADD_NON_BINDABLE)
+                            .set_proto_id(ZX_PROTOCOL_CPU_CTRL)
+                            .set_performance_states({perf_states.get(), perf_state_count})
+                            .set_inspect_vmo(device->inspector_.DuplicateVmo()));
+
+    if (st != ZX_OK) {
+      zxlogf(ERROR, "%s: DdkAdd failed, st = %d", __func__, st);
+      return st;
+    }
+
+    __UNUSED auto ptr = device.release();
+  }
+
+  return ZX_OK;
+}
+
+zx_status_t AmlCpu::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
+  DdkTransaction transaction(txn);
+  fuchsia_cpuctrl::Device::Dispatch(this, msg, &transaction);
+  return transaction.Status();
+}
+
+void AmlCpu::DdkRelease() { delete this; }
+
+zx_status_t AmlCpu::DdkSetPerformanceState(uint32_t requested_state, uint32_t* out_state) {
+  if (requested_state >= operating_points_.size()) {
+    zxlogf(ERROR, "%s: Requested performance state is out of bounds, state = %u\n", __func__,
+           requested_state);
+    return ZX_ERR_OUT_OF_RANGE;
+  }
+
+  if (!out_state) {
+    zxlogf(ERROR, "%s: out_state may not be null", __func__);
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  // There is no condition under which this function will return ZX_OK but out_state will not
+  // be requested_state so we're going to go ahead and set that up front.
+  *out_state = requested_state;
+
+  const operating_point_t& target_state = operating_points_[requested_state];
+  const operating_point_t& initial_state = operating_points_[current_pstate_];
+
+  zxlogf(INFO, "%s: Scaling from %u MHz %u mV to %u MHz %u mV", __func__,
+         initial_state.freq_hz / 1000000, initial_state.volt_uv / 1000,
+         target_state.freq_hz / 1000000, target_state.volt_uv / 1000);
+
+  if (initial_state.freq_hz == target_state.freq_hz &&
+      initial_state.volt_uv == target_state.volt_uv) {
+    // Nothing to be done.
+    return ZX_OK;
+  }
+
+  zx_status_t st;
+  if (target_state.freq_hz > initial_state.freq_hz) {
+    // If we're increasing the frequency, we need to increase the voltage first.
+    uint32_t actual_voltage;
+    st = pwr_.RequestVoltage(target_state.volt_uv, &actual_voltage);
+    if (st != ZX_OK || actual_voltage != target_state.volt_uv) {
+      zxlogf(ERROR, "%s: Failed to set cpu voltage, requested = %u, got = %u, st = %d", __func__,
+             target_state.volt_uv, actual_voltage, st);
+      return st;
+    }
+  }
+
+  // Set the frequency next.
+  st = cpuscaler_.SetRate(target_state.freq_hz);
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Could not set CPU frequency, st = %d\n", __func__, st);
+
+    // Put the voltage back if frequency scaling fails.
+    uint32_t actual_voltage;
+    zx_status_t vt_st = pwr_.RequestVoltage(initial_state.volt_uv, &actual_voltage);
+    if (vt_st != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to reset CPU voltage, st = %d, Voltage and frequency mismatch!",
+             __func__, vt_st);
+      return vt_st;
+    }
+    return st;
+  }
+
+  // If we're decreasing the frequency, then we set the voltage after the frequency has
+  // been reduced.
+  if (target_state.freq_hz < initial_state.freq_hz) {
+    // If we're increasing the frequency, we need to increase the voltage first.
+    uint32_t actual_voltage;
+    st = pwr_.RequestVoltage(target_state.volt_uv, &actual_voltage);
+    if (st != ZX_OK || actual_voltage != target_state.volt_uv) {
+      zxlogf(ERROR,
+             "%s: Failed to set cpu voltage, requested = %u, got = %u, st = %d. "
+             "Voltage and frequency mismatch!",
+             __func__, target_state.volt_uv, actual_voltage, st);
+      return st;
+    }
+  }
+
+  zxlogf(INFO, "%s: Success\n", __func__);
+
+  current_pstate_ = requested_state;
+
+  return ZX_OK;
+}
+
+zx_status_t AmlCpu::Init() {
+  zx_status_t result;
+  constexpr uint32_t kInitialPstate = 0;
+
+  result = plldiv16_.Enable();
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to enable plldiv16, st = %d", __func__, result);
+    return result;
+  }
+
+  result = cpudiv16_.Enable();
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to enable cpudiv16_, st = %d", __func__, result);
+    return result;
+  }
+
+  uint32_t min_voltage, max_voltage;
+  pwr_.GetSupportedVoltageRange(&min_voltage, &max_voltage);
+  pwr_.RegisterPowerDomain(min_voltage, max_voltage);
+
+  uint32_t actual;
+  result = DdkSetPerformanceState(kInitialPstate, &actual);
+
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to set initial performance state, st = %d", __func__, result);
+    return result;
+  }
+
+  if (actual != kInitialPstate) {
+    zxlogf(ERROR, "%s: Failed to set initial performance state, requested = %u, actual = %u",
+           __func__, kInitialPstate, actual);
+    return ZX_ERR_INTERNAL;
+  }
+
+  return ZX_OK;
+}
+
+zx_status_t AmlCpu::DdkConfigureAutoSuspend(bool enable, uint8_t requested_sleep_state) {
+  return ZX_ERR_NOT_SUPPORTED;
+}
+
+void AmlCpu::GetPerformanceStateInfo(uint32_t state,
+                                     GetPerformanceStateInfoCompleter::Sync& completer) {
+  if (state >= operating_points_.size()) {
+    zxlogf(INFO, "%s: Requested an operating point that's out of bounds, %u\n", __func__, state);
+    completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
+    return;
+  }
+
+  fuchsia_hardware_cpu_ctrl::wire::CpuPerformanceStateInfo result;
+  result.frequency_hz = operating_points_[state].freq_hz;
+  result.voltage_uv = operating_points_[state].volt_uv;
+
+  completer.ReplySuccess(result);
+}
+
+void AmlCpu::GetNumLogicalCores(GetNumLogicalCoresCompleter::Sync& completer) {
+  completer.Reply(core_count_);
+}
+
+void AmlCpu::GetLogicalCoreId(uint64_t index, GetLogicalCoreIdCompleter::Sync& completer) {
+  // Placeholder.
+  completer.Reply(0);
+}
+
+void AmlCpu::SetCpuInfo(uint32_t cpu_version_packed) {
+  const uint8_t major_revision = (cpu_version_packed >> 24) & 0xff;
+  const uint8_t minor_revision = (cpu_version_packed >> 8) & 0xff;
+  const uint8_t cpu_package_id = (cpu_version_packed >> 20) & 0x0f;
+  zxlogf(INFO, "major revision number: 0x%x", major_revision);
+  zxlogf(INFO, "minor revision number: 0x%x", minor_revision);
+  zxlogf(INFO, "cpu package id number: 0x%x", cpu_package_id);
+
+  cpu_info_.CreateUint("cpu_major_revision", major_revision, &inspector_);
+  cpu_info_.CreateUint("cpu_minor_revision", minor_revision, &inspector_);
+  cpu_info_.CreateUint("cpu_package_id", cpu_package_id, &inspector_);
+}
+
+}  // namespace amlogic_cpu
+
+static constexpr zx_driver_ops_t aml_cpu_driver_ops = []() {
+  zx_driver_ops_t result = {};
+  result.version = DRIVER_OPS_VERSION;
+  result.bind = amlogic_cpu::AmlCpu::Create;
+  return result;
+}();
+
+// clang-format off
+ZIRCON_DRIVER(aml_cpu, aml_cpu_driver_ops, "zircon", "0.1");
+
diff --git a/src/devices/example/fidl-tables/astro-cpu.cc b/src/devices/example/fidl-tables/astro-cpu.cc
new file mode 100644
index 0000000..944128c
--- /dev/null
+++ b/src/devices/example/fidl-tables/astro-cpu.cc
@@ -0,0 +1,156 @@
+// 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 <fuchsia/hardware/platform/bus/c/banjo.h>
+#include <lib/ddk/binding.h>
+#include <lib/ddk/debug.h>
+#include <lib/ddk/device.h>
+#include <lib/ddk/driver.h>
+#include <lib/ddk/platform-defs.h>
+
+#include <soc/aml-common/aml-cpu-metadata.h>
+#include <soc/aml-meson/g12a-clk.h>
+#include <soc/aml-s905d2/s905d2-hw.h>
+#include <soc/aml-s905d2/s905d2-power.h>
+
+#include "astro-gpios.h"
+#include "astro.h"
+
+namespace {
+
+constexpr amlogic_cpu::PerfDomainId kPdArmA53 = 1;
+
+constexpr pbus_mmio_t cpu_mmios[]{
+    {
+        // AOBUS
+        .base = S905D2_AOBUS_BASE,
+        .length = S905D2_AOBUS_LENGTH,
+    },
+};
+
+constexpr zx_bind_inst_t root_match[] = {
+    BI_MATCH(),
+};
+
+constexpr zx_bind_inst_t power_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_POWER),
+    BI_MATCH_IF(EQ, BIND_POWER_DOMAIN, static_cast<uint32_t>(S905d2PowerDomains::kArmCore)),
+};
+
+constexpr device_fragment_part_t power_dfp[] = {
+    {countof(root_match), root_match},
+    {countof(power_match), power_match},
+};
+
+constexpr zx_bind_inst_t clock_pll_div16_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_CLOCK),
+    BI_MATCH_IF(EQ, BIND_CLOCK_ID, g12a_clk::CLK_SYS_PLL_DIV16),
+};
+
+constexpr device_fragment_part_t clock_pll_div16_dfp[] = {
+    {countof(root_match), root_match},
+    {countof(clock_pll_div16_match), clock_pll_div16_match},
+};
+
+constexpr zx_bind_inst_t clock_cpu_div16_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_CLOCK),
+    BI_MATCH_IF(EQ, BIND_CLOCK_ID, g12a_clk::CLK_SYS_CPU_CLK_DIV16),
+};
+
+constexpr device_fragment_part_t clock_cpu_div16_dfp[] = {
+    {countof(root_match), root_match},
+    {countof(clock_cpu_div16_match), clock_cpu_div16_match},
+};
+
+constexpr zx_bind_inst_t clock_cpu_scaler_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_CLOCK),
+    BI_MATCH_IF(EQ, BIND_CLOCK_ID, g12a_clk::CLK_SYS_CPU_CLK),
+};
+
+constexpr device_fragment_part_t clock_cpu_scaler_dfp[] = {
+    {countof(root_match), root_match},
+    {countof(clock_cpu_scaler_match), clock_cpu_scaler_match},
+};
+
+constexpr device_fragment_t fragments[] = {
+    {"power-01", countof(power_dfp), power_dfp},
+    {"clock-pll-div16-01", countof(clock_pll_div16_dfp), clock_pll_div16_dfp},
+    {"clock-cpu-div16-01", countof(clock_cpu_div16_dfp), clock_cpu_div16_dfp},
+    {"clock-cpu-scaler-01", countof(clock_cpu_scaler_dfp), clock_cpu_scaler_dfp},
+};
+
+constexpr amlogic_cpu::operating_point_t operating_points[] = {
+    {.freq_hz = 100'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 250'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 500'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 667'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'000'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'200'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'398'000'000, .volt_uv = 761'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'512'000'000, .volt_uv = 791'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'608'000'000, .volt_uv = 831'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'704'000'000, .volt_uv = 861'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'896'000'000, .volt_uv = 981'000, .pd_id = kPdArmA53},
+};
+
+constexpr amlogic_cpu::perf_domain_t performance_domains[] = {
+    {.id = kPdArmA53, .core_count = 4, .relative_performance = 255, .name = "S905D2 ARM A53"},
+};
+
+static const pbus_metadata_t cpu_metadata[] = {
+    {
+        .type = DEVICE_METADATA_AML_OP_POINTS,
+        .data_buffer = reinterpret_cast<const uint8_t*>(operating_points),
+        .data_size = sizeof(operating_points),
+    },
+    {
+        .type = DEVICE_METADATA_AML_PERF_DOMAINS,
+        .data_buffer = reinterpret_cast<const uint8_t*>(performance_domains),
+        .data_size = sizeof(performance_domains),
+    },
+};
+
+constexpr pbus_dev_t cpu_dev = []() {
+  pbus_dev_t result = {};
+  result.name = "aml-cpu";
+  result.vid = PDEV_VID_GOOGLE;
+  result.pid = PDEV_PID_ASTRO;
+  result.did = PDEV_DID_GOOGLE_AMLOGIC_CPU;
+  result.metadata_list = cpu_metadata;
+  result.metadata_count = countof(cpu_metadata);
+  result.mmio_list = cpu_mmios;
+  result.mmio_count = countof(cpu_mmios);
+  return result;
+}();
+
+}  // namespace
+
+namespace astro {
+
+zx_status_t Astro::CpuInit() {
+  zx_status_t result;
+  result = gpio_impl_.ConfigOut(S905D2_PWM_D_PIN, 0);
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: ConfigOut failed: %d", __func__, result);
+    return result;
+  }
+
+  // Configure the GPIO to be Output & set it to alternate
+  // function 3 which puts in PWM_D mode.
+  result = gpio_impl_.SetAltFunction(S905D2_PWM_D_PIN, S905D2_PWM_D_FN);
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: SetAltFunction failed: %d", __func__, result);
+    return result;
+  }
+
+  result = pbus_.CompositeDeviceAdd(&cpu_dev, reinterpret_cast<uint64_t>(fragments),
+                                    countof(fragments), 1);
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to add CPU composite device, st = %d", __func__, result);
+  }
+
+  return result;
+}
+
+}  // namespace astro
diff --git a/src/devices/example/flatbuffers/aml-cpu.cc b/src/devices/example/flatbuffers/aml-cpu.cc
new file mode 100644
index 0000000..5b20ac8
--- /dev/null
+++ b/src/devices/example/flatbuffers/aml-cpu.cc
@@ -0,0 +1,402 @@
+// 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 "aml-cpu.h"
+
+#include <lib/ddk/debug.h>
+#include <lib/ddk/driver.h>
+#include <lib/ddk/platform-defs.h>
+#include <lib/device-protocol/pdev.h>
+#include <lib/inspect/cpp/inspector.h>
+#include <lib/mmio/mmio.h>
+
+#include <memory>
+#include <vector>
+
+#include <ddktl/fidl.h>
+#include <fbl/string_buffer.h>
+
+#include "src/devices/cpu/drivers/aml-cpu/aml-cpu-bind.h"
+
+namespace amlogic_cpu {
+
+namespace {
+// Fragments are provided to this driver in groups of 4. Fragments are provided as follows:
+// [4 fragments for cluster 0]
+// [4 fragments for cluster 1]
+// [...]
+// [4 fragments for cluster n]
+// Each fragment is a combination of the fixed string + id.
+constexpr size_t kFragmentsPerPfDomain = 4;
+
+constexpr zx_off_t kCpuVersionOffset = 0x220;
+
+}  // namespace
+
+zx_status_t AmlCpu::Create(void* context, zx_device_t* parent) {
+  zx_status_t st;
+  size_t actual;
+
+  // Get the metadata for the performance domains.
+  size_t perf_domain_size = 0;
+  st = device_get_metadata_size(parent, DEVICE_METADATA_AML_PERF_DOMAINS, &perf_domain_size);
+  zxlogf(DEBUG, "%s: Got AML_PERF_DOMAINS metadata size, st = %d, size = %lu", __func__, st,
+         perf_domain_size);
+
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to get performance domain count from board driver, st = %d", __func__,
+           st);
+    return st;
+  }
+
+  // Make sure that the board driver gave us an exact integer number of performance domains.
+  if (perf_domain_size % sizeof(perf_domain_t) != 0) {
+    zxlogf(ERROR,
+           "%s: Performance domain metadata from board driver is malformed. perf_domain_size = "
+           "%lu, sizeof(perf_domain_t) = %lu",
+           __func__, perf_domain_size, sizeof(perf_domain_t));
+    return ZX_ERR_INTERNAL;
+  }
+
+  const size_t num_perf_domains = perf_domain_size / sizeof(perf_domain_t);
+  std::unique_ptr<perf_domain_t[]> perf_domains =
+      std::make_unique<perf_domain_t[]>(num_perf_domains);
+  st = device_get_metadata(parent, DEVICE_METADATA_AML_PERF_DOMAINS, perf_domains.get(),
+                           perf_domain_size, &actual);
+  zxlogf(DEBUG, "%s: Got AML_PERF_DOMAINS metadata, st = %d, actual = %lu", __func__, st, actual);
+
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to get performance domain metadata from board driver, st = %d",
+           __func__, st);
+    return st;
+  }
+  if (actual != perf_domain_size) {
+    zxlogf(ERROR, "%s: Expected %lu bytes in perf domain metadata, got %lu", __func__,
+           perf_domain_size, actual);
+    return ZX_ERR_INTERNAL;
+  }
+
+  size_t op_point_size;
+  st = device_get_metadata_size(parent, DEVICE_METADATA_AML_OP_POINTS, &op_point_size);
+  zxlogf(DEBUG, "%s: Got AML_OP_POINTS metadata size, st = %d, size = %lu", __func__, st,
+         op_point_size);
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to get opp count metadata size from board driver, st = %d", __func__,
+           st);
+    return st;
+  }
+
+  if (op_point_size % sizeof(operating_point_t) != 0) {
+    zxlogf(ERROR, "%s: Operating point metadata from board driver is malformed", __func__);
+    return ZX_ERR_INTERNAL;
+  }
+
+  const size_t num_op_points = op_point_size / sizeof(operating_point_t);
+  std::unique_ptr<operating_point_t[]> operating_points =
+      std::make_unique<operating_point_t[]>(num_op_points);
+  st = device_get_metadata(parent, DEVICE_METADATA_AML_OP_POINTS, operating_points.get(),
+                           op_point_size, &actual);
+  zxlogf(DEBUG, "%s: Got AML_OP_POINTS metadata, st = %d, actual = %lu", __func__, st, actual);
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to get operating points from board driver, st = %d", __func__, st);
+    return st;
+  }
+  if (actual != op_point_size) {
+    zxlogf(ERROR, "%s: Expected %lu bytes in operating point metadata, got %lu", __func__,
+           op_point_size, actual);
+    return ZX_ERR_INTERNAL;
+  }
+  // Make sure we have the right number of fragments.
+  const uint32_t fragment_count = device_get_fragment_count(parent);
+  zxlogf(DEBUG, "%s: GetFragmentCount = %u", __func__, fragment_count);
+  if ((num_perf_domains * kFragmentsPerPfDomain) + 1 != fragment_count) {
+    zxlogf(ERROR,
+           "%s: Expected %lu fragments for each %lu performance domains for a total of %lu "
+           "fragments but got %u instead",
+           __func__, kFragmentsPerPfDomain, perf_domain_size,
+           perf_domain_size * kFragmentsPerPfDomain, fragment_count);
+    return ZX_ERR_INTERNAL;
+  }
+
+  // Map AOBUS registers
+  auto pdev = ddk::PDev::FromFragment(parent);
+  if (!pdev.is_valid()) {
+    zxlogf(ERROR, "Failed to get platform device fragment");
+    return ZX_ERR_NO_RESOURCES;
+  }
+  std::optional<ddk::MmioBuffer> mmio_buffer;
+  if ((st = pdev.MapMmio(0, &mmio_buffer)) != ZX_OK) {
+    zxlogf(ERROR, "aml-cpu: Failed to map mmio, st = %d", st);
+    return st;
+  }
+  const uint32_t cpu_version_packed = mmio_buffer->Read32(kCpuVersionOffset);
+
+  // Build and publish each performance domain.
+  for (size_t i = 0; i < num_perf_domains; i++) {
+    const perf_domain_t& perf_domain = perf_domains[i];
+
+    fbl::StringBuffer<32> fragment_name;
+    fragment_name.AppendPrintf("clock-pll-div16-%02d", perf_domain.id);
+    ddk::ClockProtocolClient pll_div16_client;
+    if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
+                                                         &pll_div16_client)) != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to create pll_div_16 clock client, st = %d", __func__, st);
+      return st;
+    }
+
+    fragment_name.Resize(0);
+    fragment_name.AppendPrintf("clock-cpu-div16-%02d", perf_domain.id);
+    ddk::ClockProtocolClient cpu_div16_client;
+    if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
+                                                         &cpu_div16_client)) != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to create cpu_div_16 clock client, st = %d", __func__, st);
+      return st;
+    }
+
+    fragment_name.Resize(0);
+    fragment_name.AppendPrintf("clock-cpu-scaler-%02d", perf_domain.id);
+    ddk::ClockProtocolClient cpu_scaler_client;
+    if ((st = ddk::ClockProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
+                                                         &cpu_scaler_client)) != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to create cpu_scaler clock client, st = %d", __func__, st);
+      return st;
+    }
+
+    fragment_name.Resize(0);
+    fragment_name.AppendPrintf("power-%02d", perf_domain.id);
+    ddk::PowerProtocolClient power_client;
+    if ((st = ddk::PowerProtocolClient::CreateFromDevice(parent, fragment_name.c_str(),
+                                                         &power_client)) != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to create power client, st = %d", __func__, st);
+      return st;
+    }
+
+    // Vector of operating points that belong to this power domain.
+    std::vector<operating_point_t> pd_op_points;
+    std::copy_if(operating_points.get(), operating_points.get() + num_op_points,
+                 std::back_inserter(pd_op_points), [&perf_domain](const operating_point_t& op) {
+                   return op.pd_id == perf_domain.id;
+                 });
+
+    // Order operating points from highest frequency to lowest because Operating Point 0 is the
+    // fastest.
+    std::sort(pd_op_points.begin(), pd_op_points.end(),
+              [](const operating_point_t& a, const operating_point_t& b) {
+                return a.freq_hz > b.freq_hz;
+              });
+
+    const size_t perf_state_count = pd_op_points.size();
+    auto perf_states = std::make_unique<device_performance_state_info_t[]>(perf_state_count);
+    for (size_t j = 0; j < perf_state_count; j++) {
+      perf_states[j].state_id = static_cast<uint8_t>(j);
+      perf_states[j].restore_latency = 0;
+    }
+
+    auto device = std::make_unique<AmlCpu>(
+        parent, std::move(pll_div16_client), std::move(cpu_div16_client),
+        std::move(cpu_scaler_client), std::move(power_client), std::move(pd_op_points),
+        perf_domain.core_count);
+
+    st = device->Init();
+    if (st != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to initialize device, st = %d", __func__, st);
+      return st;
+    }
+
+    device->SetCpuInfo(cpu_version_packed);
+
+    st = device->DdkAdd(ddk::DeviceAddArgs("cpu")
+                            .set_flags(DEVICE_ADD_NON_BINDABLE)
+                            .set_proto_id(ZX_PROTOCOL_CPU_CTRL)
+                            .set_performance_states({perf_states.get(), perf_state_count})
+                            .set_inspect_vmo(device->inspector_.DuplicateVmo()));
+
+    if (st != ZX_OK) {
+      zxlogf(ERROR, "%s: DdkAdd failed, st = %d", __func__, st);
+      return st;
+    }
+
+    __UNUSED auto ptr = device.release();
+  }
+
+  return ZX_OK;
+}
+
+zx_status_t AmlCpu::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
+  DdkTransaction transaction(txn);
+  fuchsia_cpuctrl::Device::Dispatch(this, msg, &transaction);
+  return transaction.Status();
+}
+
+void AmlCpu::DdkRelease() { delete this; }
+
+zx_status_t AmlCpu::DdkSetPerformanceState(uint32_t requested_state, uint32_t* out_state) {
+  if (requested_state >= operating_points_.size()) {
+    zxlogf(ERROR, "%s: Requested performance state is out of bounds, state = %u\n", __func__,
+           requested_state);
+    return ZX_ERR_OUT_OF_RANGE;
+  }
+
+  if (!out_state) {
+    zxlogf(ERROR, "%s: out_state may not be null", __func__);
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  // There is no condition under which this function will return ZX_OK but out_state will not
+  // be requested_state so we're going to go ahead and set that up front.
+  *out_state = requested_state;
+
+  const operating_point_t& target_state = operating_points_[requested_state];
+  const operating_point_t& initial_state = operating_points_[current_pstate_];
+
+  zxlogf(INFO, "%s: Scaling from %u MHz %u mV to %u MHz %u mV", __func__,
+         initial_state.freq_hz / 1000000, initial_state.volt_uv / 1000,
+         target_state.freq_hz / 1000000, target_state.volt_uv / 1000);
+
+  if (initial_state.freq_hz == target_state.freq_hz &&
+      initial_state.volt_uv == target_state.volt_uv) {
+    // Nothing to be done.
+    return ZX_OK;
+  }
+
+  zx_status_t st;
+  if (target_state.freq_hz > initial_state.freq_hz) {
+    // If we're increasing the frequency, we need to increase the voltage first.
+    uint32_t actual_voltage;
+    st = pwr_.RequestVoltage(target_state.volt_uv, &actual_voltage);
+    if (st != ZX_OK || actual_voltage != target_state.volt_uv) {
+      zxlogf(ERROR, "%s: Failed to set cpu voltage, requested = %u, got = %u, st = %d", __func__,
+             target_state.volt_uv, actual_voltage, st);
+      return st;
+    }
+  }
+
+  // Set the frequency next.
+  st = cpuscaler_.SetRate(target_state.freq_hz);
+  if (st != ZX_OK) {
+    zxlogf(ERROR, "%s: Could not set CPU frequency, st = %d\n", __func__, st);
+
+    // Put the voltage back if frequency scaling fails.
+    uint32_t actual_voltage;
+    zx_status_t vt_st = pwr_.RequestVoltage(initial_state.volt_uv, &actual_voltage);
+    if (vt_st != ZX_OK) {
+      zxlogf(ERROR, "%s: Failed to reset CPU voltage, st = %d, Voltage and frequency mismatch!",
+             __func__, vt_st);
+      return vt_st;
+    }
+    return st;
+  }
+
+  // If we're decreasing the frequency, then we set the voltage after the frequency has
+  // been reduced.
+  if (target_state.freq_hz < initial_state.freq_hz) {
+    // If we're increasing the frequency, we need to increase the voltage first.
+    uint32_t actual_voltage;
+    st = pwr_.RequestVoltage(target_state.volt_uv, &actual_voltage);
+    if (st != ZX_OK || actual_voltage != target_state.volt_uv) {
+      zxlogf(ERROR,
+             "%s: Failed to set cpu voltage, requested = %u, got = %u, st = %d. "
+             "Voltage and frequency mismatch!",
+             __func__, target_state.volt_uv, actual_voltage, st);
+      return st;
+    }
+  }
+
+  zxlogf(INFO, "%s: Success\n", __func__);
+
+  current_pstate_ = requested_state;
+
+  return ZX_OK;
+}
+
+zx_status_t AmlCpu::Init() {
+  zx_status_t result;
+  constexpr uint32_t kInitialPstate = 0;
+
+  result = plldiv16_.Enable();
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to enable plldiv16, st = %d", __func__, result);
+    return result;
+  }
+
+  result = cpudiv16_.Enable();
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to enable cpudiv16_, st = %d", __func__, result);
+    return result;
+  }
+
+  uint32_t min_voltage, max_voltage;
+  pwr_.GetSupportedVoltageRange(&min_voltage, &max_voltage);
+  pwr_.RegisterPowerDomain(min_voltage, max_voltage);
+
+  uint32_t actual;
+  result = DdkSetPerformanceState(kInitialPstate, &actual);
+
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to set initial performance state, st = %d", __func__, result);
+    return result;
+  }
+
+  if (actual != kInitialPstate) {
+    zxlogf(ERROR, "%s: Failed to set initial performance state, requested = %u, actual = %u",
+           __func__, kInitialPstate, actual);
+    return ZX_ERR_INTERNAL;
+  }
+
+  return ZX_OK;
+}
+
+zx_status_t AmlCpu::DdkConfigureAutoSuspend(bool enable, uint8_t requested_sleep_state) {
+  return ZX_ERR_NOT_SUPPORTED;
+}
+
+void AmlCpu::GetPerformanceStateInfo(uint32_t state,
+                                     GetPerformanceStateInfoCompleter::Sync& completer) {
+  if (state >= operating_points_.size()) {
+    zxlogf(INFO, "%s: Requested an operating point that's out of bounds, %u\n", __func__, state);
+    completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
+    return;
+  }
+
+  fuchsia_hardware_cpu_ctrl::wire::CpuPerformanceStateInfo result;
+  result.frequency_hz = operating_points_[state].freq_hz;
+  result.voltage_uv = operating_points_[state].volt_uv;
+
+  completer.ReplySuccess(result);
+}
+
+void AmlCpu::GetNumLogicalCores(GetNumLogicalCoresCompleter::Sync& completer) {
+  completer.Reply(core_count_);
+}
+
+void AmlCpu::GetLogicalCoreId(uint64_t index, GetLogicalCoreIdCompleter::Sync& completer) {
+  // Placeholder.
+  completer.Reply(0);
+}
+
+void AmlCpu::SetCpuInfo(uint32_t cpu_version_packed) {
+  const uint8_t major_revision = (cpu_version_packed >> 24) & 0xff;
+  const uint8_t minor_revision = (cpu_version_packed >> 8) & 0xff;
+  const uint8_t cpu_package_id = (cpu_version_packed >> 20) & 0x0f;
+  zxlogf(INFO, "major revision number: 0x%x", major_revision);
+  zxlogf(INFO, "minor revision number: 0x%x", minor_revision);
+  zxlogf(INFO, "cpu package id number: 0x%x", cpu_package_id);
+
+  cpu_info_.CreateUint("cpu_major_revision", major_revision, &inspector_);
+  cpu_info_.CreateUint("cpu_minor_revision", minor_revision, &inspector_);
+  cpu_info_.CreateUint("cpu_package_id", cpu_package_id, &inspector_);
+}
+
+}  // namespace amlogic_cpu
+
+static constexpr zx_driver_ops_t aml_cpu_driver_ops = []() {
+  zx_driver_ops_t result = {};
+  result.version = DRIVER_OPS_VERSION;
+  result.bind = amlogic_cpu::AmlCpu::Create;
+  return result;
+}();
+
+// clang-format off
+ZIRCON_DRIVER(aml_cpu, aml_cpu_driver_ops, "zircon", "0.1");
+
diff --git a/src/devices/example/flatbuffers/astro-cpu.cc b/src/devices/example/flatbuffers/astro-cpu.cc
new file mode 100644
index 0000000..944128c
--- /dev/null
+++ b/src/devices/example/flatbuffers/astro-cpu.cc
@@ -0,0 +1,156 @@
+// 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 <fuchsia/hardware/platform/bus/c/banjo.h>
+#include <lib/ddk/binding.h>
+#include <lib/ddk/debug.h>
+#include <lib/ddk/device.h>
+#include <lib/ddk/driver.h>
+#include <lib/ddk/platform-defs.h>
+
+#include <soc/aml-common/aml-cpu-metadata.h>
+#include <soc/aml-meson/g12a-clk.h>
+#include <soc/aml-s905d2/s905d2-hw.h>
+#include <soc/aml-s905d2/s905d2-power.h>
+
+#include "astro-gpios.h"
+#include "astro.h"
+
+namespace {
+
+constexpr amlogic_cpu::PerfDomainId kPdArmA53 = 1;
+
+constexpr pbus_mmio_t cpu_mmios[]{
+    {
+        // AOBUS
+        .base = S905D2_AOBUS_BASE,
+        .length = S905D2_AOBUS_LENGTH,
+    },
+};
+
+constexpr zx_bind_inst_t root_match[] = {
+    BI_MATCH(),
+};
+
+constexpr zx_bind_inst_t power_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_POWER),
+    BI_MATCH_IF(EQ, BIND_POWER_DOMAIN, static_cast<uint32_t>(S905d2PowerDomains::kArmCore)),
+};
+
+constexpr device_fragment_part_t power_dfp[] = {
+    {countof(root_match), root_match},
+    {countof(power_match), power_match},
+};
+
+constexpr zx_bind_inst_t clock_pll_div16_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_CLOCK),
+    BI_MATCH_IF(EQ, BIND_CLOCK_ID, g12a_clk::CLK_SYS_PLL_DIV16),
+};
+
+constexpr device_fragment_part_t clock_pll_div16_dfp[] = {
+    {countof(root_match), root_match},
+    {countof(clock_pll_div16_match), clock_pll_div16_match},
+};
+
+constexpr zx_bind_inst_t clock_cpu_div16_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_CLOCK),
+    BI_MATCH_IF(EQ, BIND_CLOCK_ID, g12a_clk::CLK_SYS_CPU_CLK_DIV16),
+};
+
+constexpr device_fragment_part_t clock_cpu_div16_dfp[] = {
+    {countof(root_match), root_match},
+    {countof(clock_cpu_div16_match), clock_cpu_div16_match},
+};
+
+constexpr zx_bind_inst_t clock_cpu_scaler_match[] = {
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_CLOCK),
+    BI_MATCH_IF(EQ, BIND_CLOCK_ID, g12a_clk::CLK_SYS_CPU_CLK),
+};
+
+constexpr device_fragment_part_t clock_cpu_scaler_dfp[] = {
+    {countof(root_match), root_match},
+    {countof(clock_cpu_scaler_match), clock_cpu_scaler_match},
+};
+
+constexpr device_fragment_t fragments[] = {
+    {"power-01", countof(power_dfp), power_dfp},
+    {"clock-pll-div16-01", countof(clock_pll_div16_dfp), clock_pll_div16_dfp},
+    {"clock-cpu-div16-01", countof(clock_cpu_div16_dfp), clock_cpu_div16_dfp},
+    {"clock-cpu-scaler-01", countof(clock_cpu_scaler_dfp), clock_cpu_scaler_dfp},
+};
+
+constexpr amlogic_cpu::operating_point_t operating_points[] = {
+    {.freq_hz = 100'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 250'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 500'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 667'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'000'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'200'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'398'000'000, .volt_uv = 761'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'512'000'000, .volt_uv = 791'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'608'000'000, .volt_uv = 831'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'704'000'000, .volt_uv = 861'000, .pd_id = kPdArmA53},
+    {.freq_hz = 1'896'000'000, .volt_uv = 981'000, .pd_id = kPdArmA53},
+};
+
+constexpr amlogic_cpu::perf_domain_t performance_domains[] = {
+    {.id = kPdArmA53, .core_count = 4, .relative_performance = 255, .name = "S905D2 ARM A53"},
+};
+
+static const pbus_metadata_t cpu_metadata[] = {
+    {
+        .type = DEVICE_METADATA_AML_OP_POINTS,
+        .data_buffer = reinterpret_cast<const uint8_t*>(operating_points),
+        .data_size = sizeof(operating_points),
+    },
+    {
+        .type = DEVICE_METADATA_AML_PERF_DOMAINS,
+        .data_buffer = reinterpret_cast<const uint8_t*>(performance_domains),
+        .data_size = sizeof(performance_domains),
+    },
+};
+
+constexpr pbus_dev_t cpu_dev = []() {
+  pbus_dev_t result = {};
+  result.name = "aml-cpu";
+  result.vid = PDEV_VID_GOOGLE;
+  result.pid = PDEV_PID_ASTRO;
+  result.did = PDEV_DID_GOOGLE_AMLOGIC_CPU;
+  result.metadata_list = cpu_metadata;
+  result.metadata_count = countof(cpu_metadata);
+  result.mmio_list = cpu_mmios;
+  result.mmio_count = countof(cpu_mmios);
+  return result;
+}();
+
+}  // namespace
+
+namespace astro {
+
+zx_status_t Astro::CpuInit() {
+  zx_status_t result;
+  result = gpio_impl_.ConfigOut(S905D2_PWM_D_PIN, 0);
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: ConfigOut failed: %d", __func__, result);
+    return result;
+  }
+
+  // Configure the GPIO to be Output & set it to alternate
+  // function 3 which puts in PWM_D mode.
+  result = gpio_impl_.SetAltFunction(S905D2_PWM_D_PIN, S905D2_PWM_D_FN);
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: SetAltFunction failed: %d", __func__, result);
+    return result;
+  }
+
+  result = pbus_.CompositeDeviceAdd(&cpu_dev, reinterpret_cast<uint64_t>(fragments),
+                                    countof(fragments), 1);
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "%s: Failed to add CPU composite device, st = %d", __func__, result);
+  }
+
+  return result;
+}
+
+}  // namespace astro