diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index c7f5bbf28..3c5c53e24 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -21,12 +21,13 @@
 #include "core/hle/service/vi/display/vi_display.h"
 #include "core/hle/service/vi/layer/vi_layer.h"
 #include "core/perf_stats.h"
+#include "core/settings.h"
 #include "video_core/renderer_base.h"
 
 namespace Service::NVFlinger {
 
-constexpr std::size_t SCREEN_REFRESH_RATE = 60;
-constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / SCREEN_REFRESH_RATE);
+constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
+constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 30);
 
 NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_timing} {
     displays.emplace_back(0, "Default");
@@ -36,13 +37,15 @@ NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_t
     displays.emplace_back(4, "Null");
 
     // Schedule the screen composition events
-    composition_event =
-        core_timing.RegisterEvent("ScreenComposition", [this](u64 userdata, s64 cycles_late) {
+    const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : frame_ticks;
+
+    composition_event = core_timing.RegisterEvent(
+        "ScreenComposition", [this, ticks](u64 userdata, s64 cycles_late) {
             Compose();
-            this->core_timing.ScheduleEvent(frame_ticks - cycles_late, composition_event);
+            this->core_timing.ScheduleEvent(ticks - cycles_late, composition_event);
         });
 
-    core_timing.ScheduleEvent(frame_ticks, composition_event);
+    core_timing.ScheduleEvent(ticks, composition_event);
 }
 
 NVFlinger::~NVFlinger() {
@@ -62,6 +65,7 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
     const auto itr =
         std::find_if(displays.begin(), displays.end(),
                      [&](const VI::Display& display) { return display.GetName() == name; });
+
     if (itr == displays.end()) {
         return {};
     }
diff --git a/src/core/settings.h b/src/core/settings.h
index d543eb32f..b84390745 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -393,6 +393,7 @@ struct Values {
     bool use_disk_shader_cache;
     bool use_accurate_gpu_emulation;
     bool use_asynchronous_gpu_emulation;
+    bool force_30fps_mode;
 
     float bg_red;
     float bg_green;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index dead9f807..17ee6b4a3 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -373,6 +373,7 @@ void Config::ReadValues() {
         ReadSetting("use_accurate_gpu_emulation", false).toBool();
     Settings::values.use_asynchronous_gpu_emulation =
         ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
+    Settings::values.force_30fps_mode = ReadSetting("force_30fps_mode", false).toBool();
 
     Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();
     Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat();
@@ -648,6 +649,7 @@ void Config::SaveValues() {
     WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
     WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
                  false);
+    WriteSetting("force_30fps_mode", Settings::values.force_30fps_mode, false);
 
     // Cast to double because Qt's written float values are not human-readable
     WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index dd1d67488..0a9883d37 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -77,6 +77,8 @@ void ConfigureGraphics::setConfiguration() {
     ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
     ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn());
     ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation);
+    ui->force_30fps_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
+    ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
     UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
                                                  Settings::values.bg_blue));
 }
@@ -90,6 +92,7 @@ void ConfigureGraphics::applyConfiguration() {
     Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
     Settings::values.use_asynchronous_gpu_emulation =
         ui->use_asynchronous_gpu_emulation->isChecked();
+    Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();
     Settings::values.bg_red = static_cast<float>(bg_color.redF());
     Settings::values.bg_green = static_cast<float>(bg_color.greenF());
     Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index c6767e0ca..15ab18ecd 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -70,6 +70,13 @@
           </property>
          </widget>
         </item>
+        <item>
+         <widget class="QCheckBox" name="force_30fps_mode">
+          <property name="text">
+           <string>Force 30 FPS mode</string>
+          </property>
+         </widget>
+        </item>
         <item>
          <layout class="QHBoxLayout" name="horizontalLayout">
           <item>