diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 416203c598..8a1861051b 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -189,6 +189,14 @@ if(ARCHITECTURE_x86_64)
     target_link_libraries(common PRIVATE xbyak::xbyak)
 endif()
 
+if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
+    target_sources(common
+        PRIVATE
+            arm64/native_clock.cpp
+            arm64/native_clock.h
+    )
+endif()
+
 if (MSVC)
   target_compile_definitions(common PRIVATE
     # The standard library doesn't provide any replacement for codecvt yet
diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp
new file mode 100644
index 0000000000..88fdba5277
--- /dev/null
+++ b/src/common/arm64/native_clock.cpp
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/arm64/native_clock.h"
+
+namespace Common::Arm64 {
+
+namespace {
+
+NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
+    return (static_cast<NativeClock::FactorType>(num) << 64) / den;
+}
+
+u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
+    return static_cast<u64>((m * factor) >> 64);
+}
+
+} // namespace
+
+NativeClock::NativeClock() {
+    const u64 host_cntfrq = GetHostCNTFRQ();
+    ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
+    us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
+    ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
+    guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
+    gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
+}
+
+std::chrono::nanoseconds NativeClock::GetTimeNS() const {
+    return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)};
+}
+
+std::chrono::microseconds NativeClock::GetTimeUS() const {
+    return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)};
+}
+
+std::chrono::milliseconds NativeClock::GetTimeMS() const {
+    return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)};
+}
+
+u64 NativeClock::GetCNTPCT() const {
+    return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor);
+}
+
+u64 NativeClock::GetGPUTick() const {
+    return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor);
+}
+
+u64 NativeClock::GetHostTicksNow() const {
+    u64 cntvct_el0 = 0;
+    asm volatile("dsb ish\n\t"
+                 "mrs %[cntvct_el0], cntvct_el0\n\t"
+                 "dsb ish\n\t"
+                 : [cntvct_el0] "=r"(cntvct_el0));
+    return cntvct_el0;
+}
+
+u64 NativeClock::GetHostTicksElapsed() const {
+    return GetHostTicksNow();
+}
+
+bool NativeClock::IsNative() const {
+    return true;
+}
+
+u64 NativeClock::GetHostCNTFRQ() {
+    u64 cntfrq_el0 = 0;
+    asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
+    return cntfrq_el0;
+}
+
+} // namespace Common::Arm64
diff --git a/src/common/arm64/native_clock.h b/src/common/arm64/native_clock.h
new file mode 100644
index 0000000000..a28b419f2a
--- /dev/null
+++ b/src/common/arm64/native_clock.h
@@ -0,0 +1,47 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/wall_clock.h"
+
+namespace Common::Arm64 {
+
+class NativeClock final : public WallClock {
+public:
+    explicit NativeClock();
+
+    std::chrono::nanoseconds GetTimeNS() const override;
+
+    std::chrono::microseconds GetTimeUS() const override;
+
+    std::chrono::milliseconds GetTimeMS() const override;
+
+    u64 GetCNTPCT() const override;
+
+    u64 GetGPUTick() const override;
+
+    u64 GetHostTicksNow() const override;
+
+    u64 GetHostTicksElapsed() const override;
+
+    bool IsNative() const override;
+
+    static u64 GetHostCNTFRQ();
+
+public:
+    using FactorType = unsigned __int128;
+
+    FactorType GetGuestCNTFRQFactor() const {
+        return guest_cntfrq_factor;
+    }
+
+private:
+    FactorType ns_cntfrq_factor;
+    FactorType us_cntfrq_factor;
+    FactorType ms_cntfrq_factor;
+    FactorType guest_cntfrq_factor;
+    FactorType gputick_cntfrq_factor;
+};
+
+} // namespace Common::Arm64
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index 71e15ab4cf..caca9a1234 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -10,6 +10,10 @@
 #include "common/x64/rdtsc.h"
 #endif
 
+#if defined(ARCHITECTURE_arm64) && defined(__linux__)
+#include "common/arm64/native_clock.h"
+#endif
+
 namespace Common {
 
 class StandardWallClock final : public WallClock {
@@ -53,7 +57,7 @@ private:
 };
 
 std::unique_ptr<WallClock> CreateOptimalClock() {
-#ifdef ARCHITECTURE_x86_64
+#if defined(ARCHITECTURE_x86_64)
     const auto& caps = GetCPUCaps();
 
     if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
@@ -64,6 +68,8 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
         // - Is not more precise than 1 GHz (1ns resolution)
         return std::make_unique<StandardWallClock>();
     }
+#elif defined(ARCHITECTURE_arm64) && defined(__linux__)
+    return std::make_unique<Arm64::NativeClock>();
 #else
     return std::make_unique<StandardWallClock>();
 #endif