[as370][clks] Add CPU clocking

Test: syn-clk-test unit tests and performance changes on subsequent
CLs.

Bug: 33796

Change-Id: Id1c515b3f0774e7a8e7a19693f36ef6ae9849a01
diff --git a/zircon/system/dev/board/as370/as370-clock.cc b/zircon/system/dev/board/as370/as370-clock.cc
index 852d805..b4026ca 100644
--- a/zircon/system/dev/board/as370/as370-clock.cc
+++ b/zircon/system/dev/board/as370/as370-clock.cc
@@ -26,10 +26,15 @@
           .base = as370::kAudioGlobalBase,
           .length = as370::kAudioGlobalSize,
       },
+      {
+          .base = as370::kCpuBase,
+          .length = as370::kCpuSize,
+      },
   };
   static const clock_id_t clock_ids[] = {
       {as370::As370Clk::kClkAvpll0},
       {as370::As370Clk::kClkAvpll1},
+      {as370::As370Clk::kClkCpu},
   };
   static const pbus_metadata_t clock_metadata[] = {
       {
diff --git a/zircon/system/dev/clk/syn-clk/syn-clk-test.cc b/zircon/system/dev/clk/syn-clk/syn-clk-test.cc
index c6c7813..beadfaf 100644
--- a/zircon/system/dev/clk/syn-clk/syn-clk-test.cc
+++ b/zircon/system/dev/clk/syn-clk/syn-clk-test.cc
@@ -12,9 +12,11 @@
 
 class SynClkTest : public SynClk {
  public:
-  SynClkTest(ddk_mock::MockMmioRegRegion& global_mmio, ddk_mock::MockMmioRegRegion& audio_mmio)
+  SynClkTest(ddk_mock::MockMmioRegRegion& global_mmio, ddk_mock::MockMmioRegRegion& audio_mmio,
+             ddk_mock::MockMmioRegRegion& cpu_mmio)
       : SynClk(nullptr, ddk::MmioBuffer(global_mmio.GetMmioBuffer()),
-               ddk::MmioBuffer(audio_mmio.GetMmioBuffer())) {}
+               ddk::MmioBuffer(audio_mmio.GetMmioBuffer()),
+               ddk::MmioBuffer(cpu_mmio.GetMmioBuffer())) {}
 };
 
 TEST(ClkSynTest, AvpllClkEnable) {
@@ -22,7 +24,8 @@
   auto audio_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kAudioGlobalSize / 4);
   ddk_mock::MockMmioRegRegion global_region(global_regs.get(), 4, as370::kGlobalSize / 4);
   ddk_mock::MockMmioRegRegion audio_region(audio_regs.get(), 4, as370::kAudioGlobalSize / 4);
-  SynClkTest test(global_region, audio_region);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(global_region, audio_region, unused);
 
   global_regs[0x0530 / 4].ExpectRead(0x00000000).ExpectWrite(0x00000001);  // Enable AVIO clock.
   global_regs[0x0088 / 4].ExpectRead(0xffffffff).ExpectWrite(0xfffffffe);  // Not sysPll power down.
@@ -40,7 +43,8 @@
   auto audio_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kAudioGlobalSize / 4);
   ddk_mock::MockMmioRegRegion global_region(global_regs.get(), 4, as370::kGlobalSize / 4);
   ddk_mock::MockMmioRegRegion audio_region(audio_regs.get(), 4, as370::kAudioGlobalSize / 4);
-  SynClkTest test(global_region, audio_region);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(global_region, audio_region, unused);
 
   audio_regs[0x0044 / 4].ExpectRead(0xffffffff).ExpectWrite(0xfffffffb);  // Disable AVPLL.
   audio_regs[0x0000 / 4].ExpectRead(0xffffffff).ExpectWrite(0xffffffdf);  // Disable AVPLL Clock.
@@ -56,7 +60,8 @@
   auto audio_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kAudioGlobalSize / 4);
   ddk_mock::MockMmioRegRegion global_region(global_regs.get(), 4, as370::kGlobalSize / 4);
   ddk_mock::MockMmioRegRegion audio_region(audio_regs.get(), 4, as370::kAudioGlobalSize / 4);
-  SynClkTest test(global_region, audio_region);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(global_region, audio_region, unused);
 
   audio_regs[0x0044 / 4].ExpectRead(0xffffffff).ExpectWrite(0xfffffff7);  // Disable AVPLL 1.
   audio_regs[0x0020 / 4].ExpectRead(0xffffffff).ExpectWrite(0xffffffdf);  // Disable AVPLL Clock.
@@ -72,7 +77,8 @@
   auto audio_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kAudioGlobalSize / 4);
   ddk_mock::MockMmioRegRegion global_region(global_regs.get(), 4, as370::kGlobalSize / 4);
   ddk_mock::MockMmioRegRegion audio_region(audio_regs.get(), 4, as370::kAudioGlobalSize / 4);
-  SynClkTest test(global_region, audio_region);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(global_region, audio_region, unused);
 
   EXPECT_NOT_OK(test.ClockImplSetRate(0, 800'000'001));  // Too high.
 }
@@ -82,7 +88,8 @@
   auto audio_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kAudioGlobalSize / 4);
   ddk_mock::MockMmioRegRegion global_region(global_regs.get(), 4, as370::kGlobalSize / 4);
   ddk_mock::MockMmioRegRegion audio_region(audio_regs.get(), 4, as370::kAudioGlobalSize / 4);
-  SynClkTest test(global_region, audio_region);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(global_region, audio_region, unused);
 
   audio_regs[0x0044 / 4].ExpectRead(0xffffffff).ExpectWrite(0xfffffffb);  // Clock disable.
   audio_regs[0x0018 / 4].ExpectRead(0x00000000).ExpectWrite(0x00000001);  // Bypass.
@@ -105,7 +112,8 @@
   auto audio_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kAudioGlobalSize / 4);
   ddk_mock::MockMmioRegRegion global_region(global_regs.get(), 4, as370::kGlobalSize / 4);
   ddk_mock::MockMmioRegRegion audio_region(audio_regs.get(), 4, as370::kAudioGlobalSize / 4);
-  SynClkTest test(global_region, audio_region);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(global_region, audio_region, unused);
 
   audio_regs[0x0044 / 4].ExpectRead(0xffffffff).ExpectWrite(0xfffffffb);  // Clock disable.
   audio_regs[0x0018 / 4].ExpectRead(0x00000000).ExpectWrite(0x00000001);  // Bypass.
@@ -131,7 +139,8 @@
   auto audio_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kAudioGlobalSize / 4);
   ddk_mock::MockMmioRegRegion global_region(global_regs.get(), 4, as370::kGlobalSize / 4);
   ddk_mock::MockMmioRegRegion audio_region(audio_regs.get(), 4, as370::kAudioGlobalSize / 4);
-  SynClkTest test(global_region, audio_region);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(global_region, audio_region, unused);
 
   audio_regs[0x0044 / 4].ExpectRead(0xffffffff).ExpectWrite(0xfffffffb);  // Clock disable.
   audio_regs[0x0018 / 4].ExpectRead(0x00000000).ExpectWrite(0x00000001);  // Bypass.
@@ -157,7 +166,8 @@
   auto audio_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kAudioGlobalSize / 4);
   ddk_mock::MockMmioRegRegion global_region(global_regs.get(), 4, as370::kGlobalSize / 4);
   ddk_mock::MockMmioRegRegion audio_region(audio_regs.get(), 4, as370::kAudioGlobalSize / 4);
-  SynClkTest test(global_region, audio_region);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(global_region, audio_region, unused);
 
   audio_regs[0x0044 / 4].ExpectRead(0xffffffff).ExpectWrite(0xfffffff7);  // Clock disable.
   audio_regs[0x0038 / 4].ExpectRead(0x00000000).ExpectWrite(0x00000001);  // Bypass.
@@ -175,4 +185,34 @@
   audio_region.VerifyAll();
 }
 
+TEST(ClkSynTest, CpuPllSetRate1800) {
+  auto cpu_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kCpuSize / 4);
+  ddk_mock::MockMmioRegRegion cpu_region(cpu_regs.get(), 4, as370::kCpuSize / 4);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(unused, unused, cpu_region);
+
+  cpu_regs[0x2000 / 4].ExpectWrite(0x00404806);
+  cpu_regs[0x2004 / 4].ExpectWrite(0x00000000);
+  cpu_regs[0x200c / 4].ExpectWrite(0x22000000);
+
+  EXPECT_OK(test.ClockImplSetRate(2, 1'800'000'000));
+
+  cpu_region.VerifyAll();
+}
+
+TEST(ClkSynTest, CpuPllSetRate1000) {
+  auto cpu_regs = std::make_unique<ddk_mock::MockMmioReg[]>(as370::kCpuSize / 4);
+  ddk_mock::MockMmioRegRegion cpu_region(cpu_regs.get(), 4, as370::kCpuSize / 4);
+  ddk_mock::MockMmioRegRegion unused(nullptr, sizeof(uint32_t), as370::kCpuSize / 4);
+  SynClkTest test(unused, unused, cpu_region);
+
+  cpu_regs[0x2000 / 4].ExpectWrite(0x00402806);
+  cpu_regs[0x2004 / 4].ExpectWrite(0x00000000);
+  cpu_regs[0x200c / 4].ExpectWrite(0x22000000);
+
+  EXPECT_OK(test.ClockImplSetRate(2, 1'000'000'000));
+
+  cpu_region.VerifyAll();
+}
+
 }  // namespace clk
diff --git a/zircon/system/dev/clk/syn-clk/syn-clk.cc b/zircon/system/dev/clk/syn-clk/syn-clk.cc
index 67e5f2e..25df873 100644
--- a/zircon/system/dev/clk/syn-clk/syn-clk.cc
+++ b/zircon/system/dev/clk/syn-clk/syn-clk.cc
@@ -4,6 +4,8 @@
 
 #include "syn-clk.h"
 
+#include <lib/device-protocol/pdev.h>
+
 #include <numeric>
 
 #include <ddk/binding.h>
@@ -15,7 +17,6 @@
 #include <fbl/auto_lock.h>
 #include <fbl/unique_ptr.h>
 #include <hwreg/bitfields.h>
-#include <lib/device-protocol/pdev.h>
 #include <soc/as370/as370-audio-regs.h>
 #include <soc/as370/as370-clk-regs.h>
 #include <soc/as370/as370-clk.h>
@@ -29,7 +30,7 @@
     return ZX_ERR_NO_RESOURCES;
   }
 
-  std::optional<ddk::MmioBuffer> global_mmio, avio_mmio;
+  std::optional<ddk::MmioBuffer> global_mmio, avio_mmio, cpu_mmio;
   auto status = pdev.MapMmio(0, &global_mmio);
   if (status != ZX_OK) {
     zxlogf(ERROR, "%s: failed to map mmio index 0 %d\n", __FILE__, status);
@@ -40,9 +41,14 @@
     zxlogf(ERROR, "%s: failed to map mmio index 1 %d\n", __FILE__, status);
     return status;
   }
+  status = pdev.MapMmio(2, &cpu_mmio);
+  if (status != ZX_OK) {
+    zxlogf(ERROR, "%s: failed to map mmio index 2 %d\n", __FILE__, status);
+    return status;
+  }
 
   std::unique_ptr<SynClk> device(
-      new SynClk(parent, *std::move(global_mmio), *std::move(avio_mmio)));
+      new SynClk(parent, *std::move(global_mmio), *std::move(avio_mmio), *std::move(cpu_mmio)));
 
   status = device->DdkAdd("synaptics-clk");
   if (status != ZX_OK) {
@@ -85,6 +91,32 @@
   return ZX_OK;
 }
 
+zx_status_t SynClk::CpuSetRate(uint64_t rate) {
+  if (rate < 1'000'000'000 || rate > 1'800'000'000) {
+    return ZX_ERR_INVALID_ARGS;
+  }
+  auto dn = static_cast<uint32_t>(rate) / 1'000 * 72 / 1'800'000;
+  CPU_WRP_PLL_REG_ctrl::Get()
+      .FromValue(0)
+      .set_FRAC_READY(1)
+      .set_MODE(0)
+      .set_DN(dn)
+      .set_DM(1)
+      .set_RESETN(1)
+      .WriteTo(&cpu_mmio_);
+  CPU_WRP_PLL_REG_ctrl1::Get().FromValue(0).set_FRAC(0).WriteTo(&cpu_mmio_);
+  CPU_WRP_PLL_REG_ctrl3::Get()
+      .FromValue(0)
+      .set_DP1(1)
+      .set_PDDP1(0)
+      .set_DP(1)
+      .set_PDDP(0)
+      .set_SLOPE(0)
+      .WriteTo(&cpu_mmio_);
+  zxlogf(TRACE, "%s %luHz dn %u  dm %u  dp %u\n", __FILE__, rate, dn, 1, 1);
+  return ZX_OK;
+}
+
 zx_status_t SynClk::AvpllSetRate(bool avpll0, uint64_t rate) {
   // rate = (frac / (max_frac+1) + dn) * ref_clk / dm / dp.
   // frac = (rate * dp * dm / ref_clk - dn) * (max_frac+1).
@@ -233,6 +265,8 @@
       return AvpllSetRate(true, hz);
     case as370::kClkAvpll1:
       return AvpllSetRate(false, hz);
+    case as370::kClkCpu:
+      return CpuSetRate(hz);
   }
   return ZX_ERR_NOT_SUPPORTED;
 }
@@ -244,6 +278,7 @@
 
   global_mmio_.reset();
   avio_mmio_.reset();
+  cpu_mmio_.reset();
 
   DdkRemove();
 }
diff --git a/zircon/system/dev/clk/syn-clk/syn-clk.h b/zircon/system/dev/clk/syn-clk/syn-clk.h
index ce3d223..5311a67 100644
--- a/zircon/system/dev/clk/syn-clk/syn-clk.h
+++ b/zircon/system/dev/clk/syn-clk/syn-clk.h
@@ -5,11 +5,12 @@
 #ifndef ZIRCON_SYSTEM_DEV_CLK_SYN_CLK_SYN_CLK_H_
 #define ZIRCON_SYSTEM_DEV_CLK_SYN_CLK_SYN_CLK_H_
 
+#include <lib/mmio/mmio.h>
+#include <lib/zircon-internal/thread_annotations.h>
+
 #include <ddktl/device.h>
 #include <ddktl/protocol/clockimpl.h>
 #include <fbl/mutex.h>
-#include <lib/mmio/mmio.h>
-#include <lib/zircon-internal/thread_annotations.h>
 #include <soc/as370/as370-clk.h>
 
 namespace clk {
@@ -37,18 +38,22 @@
 
   // Protected for unit tests.
  protected:
-  SynClk(zx_device_t* parent, ddk::MmioBuffer global_mmio, ddk::MmioBuffer avio_mmio)
+  SynClk(zx_device_t* parent, ddk::MmioBuffer global_mmio, ddk::MmioBuffer avio_mmio,
+         ddk::MmioBuffer cpu_mmio)
       : DeviceType(parent),
         global_mmio_(std::move(global_mmio)),
-        avio_mmio_(std::move(avio_mmio)) {}
+        avio_mmio_(std::move(avio_mmio)),
+        cpu_mmio_(std::move(cpu_mmio)) {}
 
  private:
   zx_status_t AvpllClkEnable(bool avpll0, bool enable);
   zx_status_t AvpllSetRate(bool avpll0, uint64_t rate);
+  zx_status_t CpuSetRate(uint64_t rate);
 
   fbl::Mutex lock_;
   ddk::MmioBuffer global_mmio_ TA_GUARDED(lock_);
   ddk::MmioBuffer avio_mmio_ TA_GUARDED(lock_);
+  ddk::MmioBuffer cpu_mmio_ TA_GUARDED(lock_);
 };
 
 }  // namespace clk
diff --git a/zircon/system/dev/lib/as370/include/soc/as370/as370-clk-regs.h b/zircon/system/dev/lib/as370/include/soc/as370/as370-clk-regs.h
index f3a2e75..1c03901 100644
--- a/zircon/system/dev/lib/as370/include/soc/as370/as370-clk-regs.h
+++ b/zircon/system/dev/lib/as370/include/soc/as370/as370-clk-regs.h
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#pragma once
+#ifndef ZIRCON_SYSTEM_DEV_LIB_AS370_INCLUDE_SOC_AS370_AS370_CLK_REGS_H_
+#define ZIRCON_SYSTEM_DEV_LIB_AS370_INCLUDE_SOC_AS370_AS370_CLK_REGS_H_
+
+#include <zircon/types.h>
 
 #include <hwreg/bitfields.h>
-#include <zircon/types.h>
 
 // GBL Global Control Registers.
 
@@ -31,3 +33,38 @@
   DEF_BIT(0, ClkEn);
   static auto Get() { return hwreg::RegisterAddr<avioSysClk_ctrl>(0x0530); }
 };
+
+// CPU Sybsystem Registers.
+
+class CPU_WRP_PLL_REG_ctrl : public hwreg::RegisterBase<CPU_WRP_PLL_REG_ctrl, uint32_t> {
+ public:
+  DEF_BIT(22, FRAC_READY);
+  DEF_BIT(21, READY_BP);
+  DEF_FIELD(20, 19, MODE);
+  DEF_FIELD(18, 8, DN);
+  DEF_FIELD(7, 2, DM);
+  DEF_BIT(1, RESETN);
+  DEF_BIT(0, PD);
+  static auto Get() { return hwreg::RegisterAddr<CPU_WRP_PLL_REG_ctrl>(0x2000); }
+};
+
+struct CPU_WRP_PLL_REG_ctrl1 : public hwreg::RegisterBase<CPU_WRP_PLL_REG_ctrl1, uint32_t> {
+  DEF_FIELD(23, 0, FRAC);
+  static auto Get() { return hwreg::RegisterAddr<CPU_WRP_PLL_REG_ctrl1>(0x2004); }
+};
+
+struct CPU_WRP_PLL_REG_ctrl2 : public hwreg::RegisterBase<CPU_WRP_PLL_REG_ctrl2, uint32_t> {
+  DEF_FIELD(10, 0, SSRATE);
+  static auto Get() { return hwreg::RegisterAddr<CPU_WRP_PLL_REG_ctrl2>(0x2008); }
+};
+
+struct CPU_WRP_PLL_REG_ctrl3 : public hwreg::RegisterBase<CPU_WRP_PLL_REG_ctrl3, uint32_t> {
+  DEF_FIELD(31, 29, DP1);
+  DEF_BIT(28, PDDP1);
+  DEF_FIELD(27, 25, DP);
+  DEF_BIT(24, PDDP);
+  DEF_FIELD(23, 0, SLOPE);
+  static auto Get() { return hwreg::RegisterAddr<CPU_WRP_PLL_REG_ctrl3>(0x200c); }
+};
+
+#endif  // ZIRCON_SYSTEM_DEV_LIB_AS370_INCLUDE_SOC_AS370_AS370_CLK_REGS_H_
diff --git a/zircon/system/dev/lib/as370/include/soc/as370/as370-clk.h b/zircon/system/dev/lib/as370/include/soc/as370/as370-clk.h
index 6bc3c38..029ac81 100644
--- a/zircon/system/dev/lib/as370/include/soc/as370/as370-clk.h
+++ b/zircon/system/dev/lib/as370/include/soc/as370/as370-clk.h
@@ -2,13 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#pragma once
+#ifndef ZIRCON_SYSTEM_DEV_LIB_AS370_INCLUDE_SOC_AS370_AS370_CLK_H_
+#define ZIRCON_SYSTEM_DEV_LIB_AS370_INCLUDE_SOC_AS370_AS370_CLK_H_
 
 namespace as370 {
 
 enum As370Clk {
   kClkAvpll0,
   kClkAvpll1,
+  kClkCpu,
 };
 
 }  // namespace as370
+
+#endif  // ZIRCON_SYSTEM_DEV_LIB_AS370_INCLUDE_SOC_AS370_AS370_CLK_H_
diff --git a/zircon/system/dev/lib/as370/include/soc/as370/as370-hw.h b/zircon/system/dev/lib/as370/include/soc/as370/as370-hw.h
index 882b182..780be68 100644
--- a/zircon/system/dev/lib/as370/include/soc/as370/as370-hw.h
+++ b/zircon/system/dev/lib/as370/include/soc/as370/as370-hw.h
@@ -25,6 +25,10 @@
 constexpr uint32_t kAudioI2sBase = 0xf744'0000;
 constexpr uint32_t kAudioI2sSize = fbl::round_up<uint32_t, uint32_t>(0x2'0000, PAGE_SIZE);
 
+// CPU Sub-System Registers (CPUSS) Registers.
+constexpr uint32_t kCpuBase = 0xf792'0000;
+constexpr uint32_t kCpuSize = fbl::round_up<uint32_t, uint32_t>(0x2'0000, PAGE_SIZE);
+
 constexpr uint32_t kDhubIrq = 32 + 11;
 
 }  // namespace as370