diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index 69247b166..73846ed91 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -76,6 +76,9 @@ void Config::ReadValues() {
             Settings::values.analogs[i] = default_param;
     }
 
+    Settings::values.motion_device = sdl2_config->Get(
+        "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
+
     // Core
     Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
 
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index b0a0ebd3b..9ea779dd8 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -43,7 +43,7 @@ button_zr=
 button_home=
 
 # for analog input, the following devices are available:
-#  - "analog_from_button" (default) for emulating analog input from direction buttons.  Required parameters:
+#  - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
 #      - "up", "down", "left", "right": sub-devices for each direction.
 #          Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
 #      - "modifier": sub-devices as a modifier.
@@ -56,6 +56,12 @@ button_home=
 circle_pad=
 c_stick=
 
+# for motion input, the following devices are available:
+#  - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
+#      - "update_period": update period in milliseconds (default to 100)
+#      - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
+motion_device=
+
 [Core]
 # Whether to use the Just-In-Time (JIT) compiler for CPU emulation
 # 0: Interpreter (slow), 1 (default): JIT (fast)
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index b0f808399..25643715a 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -16,11 +16,12 @@
 #include "core/settings.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
+#include "input_common/motion_emu.h"
 #include "network/network.h"
 
 void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
     TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
-    motion_emu->Tilt(x, y);
+    InputCommon::GetMotionEmu()->Tilt(x, y);
 }
 
 void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
@@ -32,9 +33,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
         }
     } else if (button == SDL_BUTTON_RIGHT) {
         if (state == SDL_PRESSED) {
-            motion_emu->BeginTilt(x, y);
+            InputCommon::GetMotionEmu()->BeginTilt(x, y);
         } else {
-            motion_emu->EndTilt();
+            InputCommon::GetMotionEmu()->EndTilt();
         }
     }
 }
@@ -61,8 +62,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
     InputCommon::Init();
     Network::Init();
 
-    motion_emu = std::make_unique<Motion::MotionEmu>(*this);
-
     SDL_SetMainReady();
 
     // Initialize the window
@@ -117,7 +116,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
 EmuWindow_SDL2::~EmuWindow_SDL2() {
     SDL_GL_DeleteContext(gl_context);
     SDL_Quit();
-    motion_emu = nullptr;
 
     Network::Shutdown();
     InputCommon::Shutdown();
diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h
index 1ce2991f7..3664d2fbe 100644
--- a/src/citra/emu_window/emu_window_sdl2.h
+++ b/src/citra/emu_window/emu_window_sdl2.h
@@ -7,7 +7,6 @@
 #include <memory>
 #include <utility>
 #include "core/frontend/emu_window.h"
-#include "core/frontend/motion_emu.h"
 
 struct SDL_Window;
 
@@ -57,7 +56,4 @@ private:
     using SDL_GLContext = void*;
     /// The OpenGL context associated with the window
     SDL_GLContext gl_context;
-
-    /// Motion sensors emulation
-    std::unique_ptr<Motion::MotionEmu> motion_emu;
 };
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index 30554890f..7107bfc60 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -17,6 +17,7 @@
 #include "core/settings.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
+#include "input_common/motion_emu.h"
 #include "network/network.h"
 
 EmuThread::EmuThread(GRenderWindow* render_window)
@@ -201,7 +202,6 @@ qreal GRenderWindow::windowPixelRatio() {
 }
 
 void GRenderWindow::closeEvent(QCloseEvent* event) {
-    motion_emu = nullptr;
     emit Closed();
     QWidget::closeEvent(event);
 }
@@ -221,7 +221,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
         this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
                            static_cast<unsigned>(pos.y() * pixelRatio));
     } else if (event->button() == Qt::RightButton) {
-        motion_emu->BeginTilt(pos.x(), pos.y());
+        InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
     }
 }
 
@@ -230,14 +230,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
     qreal pixelRatio = windowPixelRatio();
     this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
                      std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
-    motion_emu->Tilt(pos.x(), pos.y());
+    InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
 }
 
 void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
     if (event->button() == Qt::LeftButton)
         this->TouchReleased();
     else if (event->button() == Qt::RightButton)
-        motion_emu->EndTilt();
+        InputCommon::GetMotionEmu()->EndTilt();
 }
 
 void GRenderWindow::focusOutEvent(QFocusEvent* event) {
@@ -290,13 +290,11 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(
 }
 
 void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
-    motion_emu = std::make_unique<Motion::MotionEmu>(*this);
     this->emu_thread = emu_thread;
     child->DisablePainting();
 }
 
 void GRenderWindow::OnEmulationStopping() {
-    motion_emu = nullptr;
     emu_thread = nullptr;
     child->EnablePainting();
 }
diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h
index 4b3a3b3cc..6974edcbb 100644
--- a/src/citra_qt/bootmanager.h
+++ b/src/citra_qt/bootmanager.h
@@ -12,7 +12,6 @@
 #include "common/thread.h"
 #include "core/core.h"
 #include "core/frontend/emu_window.h"
-#include "core/frontend/motion_emu.h"
 
 class QKeyEvent;
 class QScreen;
@@ -158,9 +157,6 @@ private:
 
     EmuThread* emu_thread;
 
-    /// Motion sensors emulation
-    std::unique_ptr<Motion::MotionEmu> motion_emu;
-
 protected:
     void showEvent(QShowEvent* event) override;
 };
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 75abb4ce6..6e42db007 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -57,6 +57,11 @@ void Config::ReadValues() {
             Settings::values.analogs[i] = default_param;
     }
 
+    Settings::values.motion_device =
+        qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
+            .toString()
+            .toStdString();
+
     qt_config->endGroup();
 
     qt_config->beginGroup("Core");
@@ -203,6 +208,7 @@ void Config::SaveValues() {
         qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
                             QString::fromStdString(Settings::values.analogs[i]));
     }
+    qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
     qt_config->endGroup();
 
     qt_config->beginGroup("Core");
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 0a6f97e4b..53bd50eb2 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -33,7 +33,6 @@ set(SRCS
             frontend/camera/interface.cpp
             frontend/emu_window.cpp
             frontend/framebuffer_layout.cpp
-            frontend/motion_emu.cpp
             gdbstub/gdbstub.cpp
             hle/config_mem.cpp
             hle/applets/applet.cpp
@@ -226,7 +225,6 @@ set(HEADERS
             frontend/emu_window.h
             frontend/framebuffer_layout.h
             frontend/input.h
-            frontend/motion_emu.h
             gdbstub/gdbstub.h
             hle/config_mem.h
             hle/function_wrappers.h
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 4f7d54a33..60b20d4e2 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -62,29 +62,6 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
     TouchPressed(framebuffer_x, framebuffer_y);
 }
 
-void EmuWindow::AccelerometerChanged(float x, float y, float z) {
-    constexpr float coef = 512;
-
-    std::lock_guard<std::mutex> lock(accel_mutex);
-
-    // TODO(wwylele): do a time stretch as it in GyroscopeChanged
-    // The time stretch formula should be like
-    // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
-    accel_x = static_cast<s16>(x * coef);
-    accel_y = static_cast<s16>(y * coef);
-    accel_z = static_cast<s16>(z * coef);
-}
-
-void EmuWindow::GyroscopeChanged(float x, float y, float z) {
-    constexpr float FULL_FPS = 60;
-    float coef = GetGyroscopeRawToDpsCoefficient();
-    float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
-    std::lock_guard<std::mutex> lock(gyro_mutex);
-    gyro_x = static_cast<s16>(x * coef * stretch);
-    gyro_y = static_cast<s16>(y * coef * stretch);
-    gyro_z = static_cast<s16>(z * coef * stretch);
-}
-
 void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
     Layout::FramebufferLayout layout;
     if (Settings::values.custom_layout == true) {
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 9414123a4..7bdee251c 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -68,27 +68,6 @@ public:
      */
     void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
 
-    /**
-     * Signal accelerometer state has changed.
-     * @param x X-axis accelerometer value
-     * @param y Y-axis accelerometer value
-     * @param z Z-axis accelerometer value
-     * @note all values are in unit of g (gravitational acceleration).
-     *    e.g. x = 1.0 means 9.8m/s^2 in x direction.
-     * @see GetAccelerometerState for axis explanation.
-     */
-    void AccelerometerChanged(float x, float y, float z);
-
-    /**
-     * Signal gyroscope state has changed.
-     * @param x X-axis accelerometer value
-     * @param y Y-axis accelerometer value
-     * @param z Z-axis accelerometer value
-     * @note all values are in deg/sec.
-     * @see GetGyroscopeState for axis explanation.
-     */
-    void GyroscopeChanged(float x, float y, float z);
-
     /**
      * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
      * @note This should be called by the core emu thread to get a state set by the window thread.
@@ -100,52 +79,6 @@ public:
         return std::make_tuple(touch_x, touch_y, touch_pressed);
     }
 
-    /**
-     * Gets the current accelerometer state (acceleration along each three axis).
-     * Axis explained:
-     *   +x is the same direction as LEFT on D-pad.
-     *   +y is normal to the touch screen, pointing outward.
-     *   +z is the same direction as UP on D-pad.
-     * Units:
-     *   1 unit of return value = 1/512 g (measured by hw test),
-     *   where g is the gravitational acceleration (9.8 m/sec2).
-     * @note This should be called by the core emu thread to get a state set by the window thread.
-     * @return std::tuple of (x, y, z)
-     */
-    std::tuple<s16, s16, s16> GetAccelerometerState() {
-        std::lock_guard<std::mutex> lock(accel_mutex);
-        return std::make_tuple(accel_x, accel_y, accel_z);
-    }
-
-    /**
-     * Gets the current gyroscope state (angular rates about each three axis).
-     * Axis explained:
-     *   +x is the same direction as LEFT on D-pad.
-     *   +y is normal to the touch screen, pointing outward.
-     *   +z is the same direction as UP on D-pad.
-     * Orientation is determined by right-hand rule.
-     * Units:
-     *   1 unit of return value = (1/coef) deg/sec,
-     *   where coef is the return value of GetGyroscopeRawToDpsCoefficient().
-     * @note This should be called by the core emu thread to get a state set by the window thread.
-     * @return std::tuple of (x, y, z)
-     */
-    std::tuple<s16, s16, s16> GetGyroscopeState() {
-        std::lock_guard<std::mutex> lock(gyro_mutex);
-        return std::make_tuple(gyro_x, gyro_y, gyro_z);
-    }
-
-    /**
-     * Gets the coefficient for units conversion of gyroscope state.
-     * The conversion formula is r = coefficient * v,
-     * where v is angular rate in deg/sec,
-     * and r is the gyroscope state.
-     * @return float-type coefficient
-     */
-    f32 GetGyroscopeRawToDpsCoefficient() const {
-        return 14.375f; // taken from hw test, and gyroscope's document
-    }
-
     /**
      * Returns currently active configuration.
      * @note Accesses to the returned object need not be consistent because it may be modified in
@@ -187,12 +120,6 @@ protected:
         touch_x = 0;
         touch_y = 0;
         touch_pressed = false;
-        accel_x = 0;
-        accel_y = -512;
-        accel_z = 0;
-        gyro_x = 0;
-        gyro_y = 0;
-        gyro_z = 0;
     }
     virtual ~EmuWindow() {}
 
@@ -255,16 +182,6 @@ private:
     u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
     u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
 
-    std::mutex accel_mutex;
-    s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
-    s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
-    s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units
-
-    std::mutex gyro_mutex;
-    s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units
-    s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units
-    s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units
-
     /**
      * Clip the provided coordinates to be inside the touchscreen area.
      */
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 0a5713dc0..5916a901d 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -11,6 +11,7 @@
 #include <utility>
 #include "common/logging/log.h"
 #include "common/param_package.h"
+#include "common/vector_math.h"
 
 namespace Input {
 
@@ -107,4 +108,22 @@ using ButtonDevice = InputDevice<bool>;
  */
 using AnalogDevice = InputDevice<std::tuple<float, float>>;
 
+/**
+ * A motion device is an input device that returns a tuple of accelerometer state vector and
+ * gyroscope state vector.
+ *
+ * For both vectors:
+ *   x+ is the same direction as LEFT on D-pad.
+ *   y+ is normal to the touch screen, pointing outward.
+ *   z+ is the same direction as UP on D-pad.
+ *
+ * For accelerometer state vector
+ *   Units: g (gravitational acceleration)
+ *
+ * For gyroscope state vector:
+ *   Orientation is determined by right-hand rule.
+ *   Units: deg/sec
+ */
+using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>;
+
 } // namespace Input
diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp
deleted file mode 100644
index 9a5b3185d..000000000
--- a/src/core/frontend/motion_emu.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/math_util.h"
-#include "common/quaternion.h"
-#include "core/frontend/emu_window.h"
-#include "core/frontend/motion_emu.h"
-
-namespace Motion {
-
-static constexpr int update_millisecond = 100;
-static constexpr auto update_duration =
-    std::chrono::duration_cast<std::chrono::steady_clock::duration>(
-        std::chrono::milliseconds(update_millisecond));
-
-MotionEmu::MotionEmu(EmuWindow& emu_window)
-    : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {}
-
-MotionEmu::~MotionEmu() {
-    if (motion_emu_thread.joinable()) {
-        shutdown_event.Set();
-        motion_emu_thread.join();
-    }
-}
-
-void MotionEmu::MotionEmuThread(EmuWindow& emu_window) {
-    auto update_time = std::chrono::steady_clock::now();
-    Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
-    Math::Quaternion<float> old_q;
-
-    while (!shutdown_event.WaitUntil(update_time)) {
-        update_time += update_duration;
-        old_q = q;
-
-        {
-            std::lock_guard<std::mutex> guard(tilt_mutex);
-
-            // Find the quaternion describing current 3DS tilting
-            q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
-                               tilt_angle);
-        }
-
-        auto inv_q = q.Inverse();
-
-        // Set the gravity vector in world space
-        auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
-
-        // Find the angular rate vector in world space
-        auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
-        angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
-
-        // Transform the two vectors from world space to 3DS space
-        gravity = QuaternionRotate(inv_q, gravity);
-        angular_rate = QuaternionRotate(inv_q, angular_rate);
-
-        // Update the sensor state
-        emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z);
-        emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z);
-    }
-}
-
-void MotionEmu::BeginTilt(int x, int y) {
-    mouse_origin = Math::MakeVec(x, y);
-    is_tilting = true;
-}
-
-void MotionEmu::Tilt(int x, int y) {
-    constexpr float SENSITIVITY = 0.01f;
-    auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
-    if (is_tilting) {
-        std::lock_guard<std::mutex> guard(tilt_mutex);
-        if (mouse_move.x == 0 && mouse_move.y == 0) {
-            tilt_angle = 0;
-        } else {
-            tilt_direction = mouse_move.Cast<float>();
-            tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f,
-                                         MathUtil::PI * 0.5f);
-        }
-    }
-}
-
-void MotionEmu::EndTilt() {
-    std::lock_guard<std::mutex> guard(tilt_mutex);
-    tilt_angle = 0;
-    is_tilting = false;
-}
-
-} // namespace Motion
diff --git a/src/core/frontend/motion_emu.h b/src/core/frontend/motion_emu.h
deleted file mode 100644
index 99d41a726..000000000
--- a/src/core/frontend/motion_emu.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-#include "common/thread.h"
-#include "common/vector_math.h"
-
-class EmuWindow;
-
-namespace Motion {
-
-class MotionEmu final {
-public:
-    MotionEmu(EmuWindow& emu_window);
-    ~MotionEmu();
-
-    /**
-     * Signals that a motion sensor tilt has begun.
-     * @param x the x-coordinate of the cursor
-     * @param y the y-coordinate of the cursor
-     */
-    void BeginTilt(int x, int y);
-
-    /**
-     * Signals that a motion sensor tilt is occurring.
-     * @param x the x-coordinate of the cursor
-     * @param y the y-coordinate of the cursor
-     */
-    void Tilt(int x, int y);
-
-    /**
-     * Signals that a motion sensor tilt has ended.
-     */
-    void EndTilt();
-
-private:
-    Math::Vec2<int> mouse_origin;
-
-    std::mutex tilt_mutex;
-    Math::Vec2<float> tilt_direction;
-    float tilt_angle = 0;
-
-    bool is_tilting = false;
-
-    Common::Event shutdown_event;
-    std::thread motion_emu_thread;
-
-    void MotionEmuThread(EmuWindow& emu_window);
-};
-
-} // namespace Motion
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 2014b8461..31f34a7ae 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -7,6 +7,7 @@
 #include <cmath>
 #include <memory>
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/core_timing.h"
 #include "core/frontend/emu_window.h"
 #include "core/frontend/input.h"
@@ -50,10 +51,14 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
 constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
 constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
 
+constexpr float accelerometer_coef = 512.0f; // measured from hw test result
+constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
+
 static std::atomic<bool> is_device_reload_pending;
 static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
     buttons;
 static std::unique_ptr<Input::AnalogDevice> circle_pad;
+static std::unique_ptr<Input::MotionDevice> motion_device;
 
 DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
     // 30 degree and 60 degree are angular thresholds for directions
@@ -90,6 +95,7 @@ static void LoadInputDevices() {
                    buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
     circle_pad = Input::CreateDevice<Input::AnalogDevice>(
         Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
+    motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device);
 }
 
 static void UnloadInputDevices() {
@@ -97,6 +103,7 @@ static void UnloadInputDevices() {
         button.reset();
     }
     circle_pad.reset();
+    motion_device.reset();
 }
 
 static void UpdatePadCallback(u64 userdata, int cycles_late) {
@@ -193,10 +200,19 @@ static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
     mem->accelerometer.index = next_accelerometer_index;
     next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
 
+    Math::Vec3<float> accel;
+    std::tie(accel, std::ignore) = motion_device->GetStatus();
+    accel *= accelerometer_coef;
+    // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback
+    // The time stretch formula should be like
+    // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
+
     AccelerometerDataEntry& accelerometer_entry =
         mem->accelerometer.entries[mem->accelerometer.index];
-    std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) =
-        VideoCore::g_emu_window->GetAccelerometerState();
+
+    accelerometer_entry.x = static_cast<s16>(accel.x);
+    accelerometer_entry.y = static_cast<s16>(accel.y);
+    accelerometer_entry.z = static_cast<s16>(accel.z);
 
     // Make up "raw" entry
     // TODO(wwylele):
@@ -227,8 +243,14 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
     next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size();
 
     GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
-    std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) =
-        VideoCore::g_emu_window->GetGyroscopeState();
+
+    Math::Vec3<float> gyro;
+    std::tie(std::ignore, gyro) = motion_device->GetStatus();
+    double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
+    gyro *= gyroscope_coef * stretch;
+    gyroscope_entry.x = static_cast<s16>(gyro.x);
+    gyroscope_entry.y = static_cast<s16>(gyro.y);
+    gyroscope_entry.z = static_cast<s16>(gyro.z);
 
     // Make up "raw" entry
     mem->gyroscope.raw_entry.x = gyroscope_entry.x;
@@ -326,7 +348,7 @@ void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) {
 
     cmd_buff[1] = RESULT_SUCCESS.raw;
 
-    f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient();
+    f32 coef = gyroscope_coef;
     memcpy(&cmd_buff[2], &coef, 4);
 }
 
diff --git a/src/core/settings.h b/src/core/settings.h
index ee16bb90a..7e15b119b 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -79,6 +79,7 @@ struct Values {
     // Controls
     std::array<std::string, NativeButton::NumButtons> buttons;
     std::array<std::string, NativeAnalog::NumAnalogs> analogs;
+    std::string motion_device;
 
     // Core
     bool use_cpu_jit;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index e3e36ada7..92792a702 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -2,12 +2,14 @@ set(SRCS
             analog_from_button.cpp
             keyboard.cpp
             main.cpp
+            motion_emu.cpp
             )
 
 set(HEADERS
             analog_from_button.h
             keyboard.h
             main.h
+            motion_emu.h
             )
 
 if(SDL2_FOUND)
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 699f41e6b..557353740 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -7,6 +7,7 @@
 #include "input_common/analog_from_button.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
+#include "input_common/motion_emu.h"
 #ifdef HAVE_SDL2
 #include "input_common/sdl/sdl.h"
 #endif
@@ -14,12 +15,16 @@
 namespace InputCommon {
 
 static std::shared_ptr<Keyboard> keyboard;
+static std::shared_ptr<MotionEmu> motion_emu;
 
 void Init() {
-    keyboard = std::make_shared<InputCommon::Keyboard>();
+    keyboard = std::make_shared<Keyboard>();
     Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
     Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
-                                                std::make_shared<InputCommon::AnalogFromButton>());
+                                                std::make_shared<AnalogFromButton>());
+    motion_emu = std::make_shared<MotionEmu>();
+    Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
+
 #ifdef HAVE_SDL2
     SDL::Init();
 #endif
@@ -29,6 +34,8 @@ void Shutdown() {
     Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
     keyboard.reset();
     Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
+    Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
+    motion_emu.reset();
 
 #ifdef HAVE_SDL2
     SDL::Shutdown();
@@ -39,6 +46,10 @@ Keyboard* GetKeyboard() {
     return keyboard.get();
 }
 
+MotionEmu* GetMotionEmu() {
+    return motion_emu.get();
+}
+
 std::string GenerateKeyboardParam(int key_code) {
     Common::ParamPackage param{
         {"engine", "keyboard"}, {"code", std::to_string(key_code)},
diff --git a/src/input_common/main.h b/src/input_common/main.h
index e7fb3890e..5604f0fa8 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -19,6 +19,11 @@ class Keyboard;
 /// Gets the keyboard button device factory.
 Keyboard* GetKeyboard();
 
+class MotionEmu;
+
+/// Gets the motion emulation factory.
+MotionEmu* GetMotionEmu();
+
 /// Generates a serialized param package for creating a keyboard button device
 std::string GenerateKeyboardParam(int key_code);
 
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
new file mode 100644
index 000000000..a1761f184
--- /dev/null
+++ b/src/input_common/motion_emu.cpp
@@ -0,0 +1,165 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <mutex>
+#include <thread>
+#include <tuple>
+#include "common/math_util.h"
+#include "common/quaternion.h"
+#include "common/thread.h"
+#include "common/vector_math.h"
+#include "input_common/motion_emu.h"
+
+namespace InputCommon {
+
+// Implementation class of the motion emulation device
+class MotionEmuDevice {
+public:
+    MotionEmuDevice(int update_millisecond, float sensitivity)
+        : update_millisecond(update_millisecond),
+          update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
+              std::chrono::milliseconds(update_millisecond))),
+          sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
+
+    ~MotionEmuDevice() {
+        if (motion_emu_thread.joinable()) {
+            shutdown_event.Set();
+            motion_emu_thread.join();
+        }
+    }
+
+    void BeginTilt(int x, int y) {
+        mouse_origin = Math::MakeVec(x, y);
+        is_tilting = true;
+    }
+
+    void Tilt(int x, int y) {
+        auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
+        if (is_tilting) {
+            std::lock_guard<std::mutex> guard(tilt_mutex);
+            if (mouse_move.x == 0 && mouse_move.y == 0) {
+                tilt_angle = 0;
+            } else {
+                tilt_direction = mouse_move.Cast<float>();
+                tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f,
+                                             MathUtil::PI * 0.5f);
+            }
+        }
+    }
+
+    void EndTilt() {
+        std::lock_guard<std::mutex> guard(tilt_mutex);
+        tilt_angle = 0;
+        is_tilting = false;
+    }
+
+    std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() {
+        std::lock_guard<std::mutex> guard(status_mutex);
+        return status;
+    }
+
+private:
+    const int update_millisecond;
+    const std::chrono::steady_clock::duration update_duration;
+    const float sensitivity;
+
+    Math::Vec2<int> mouse_origin;
+
+    std::mutex tilt_mutex;
+    Math::Vec2<float> tilt_direction;
+    float tilt_angle = 0;
+
+    bool is_tilting = false;
+
+    Common::Event shutdown_event;
+    std::thread motion_emu_thread;
+
+    std::tuple<Math::Vec3<float>, Math::Vec3<float>> status;
+    std::mutex status_mutex;
+
+    void MotionEmuThread() {
+        auto update_time = std::chrono::steady_clock::now();
+        Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
+        Math::Quaternion<float> old_q;
+
+        while (!shutdown_event.WaitUntil(update_time)) {
+            update_time += update_duration;
+            old_q = q;
+
+            {
+                std::lock_guard<std::mutex> guard(tilt_mutex);
+
+                // Find the quaternion describing current 3DS tilting
+                q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
+                                   tilt_angle);
+            }
+
+            auto inv_q = q.Inverse();
+
+            // Set the gravity vector in world space
+            auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
+
+            // Find the angular rate vector in world space
+            auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
+            angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
+
+            // Transform the two vectors from world space to 3DS space
+            gravity = QuaternionRotate(inv_q, gravity);
+            angular_rate = QuaternionRotate(inv_q, angular_rate);
+
+            // Update the sensor state
+            {
+                std::lock_guard<std::mutex> guard(status_mutex);
+                status = std::make_tuple(gravity, angular_rate);
+            }
+        }
+    }
+};
+
+// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as
+// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory
+// can forward all the inputs to the implementation only when it is valid.
+class MotionEmuDeviceWrapper : public Input::MotionDevice {
+public:
+    MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
+        device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
+    }
+
+    std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const {
+        return device->GetStatus();
+    }
+
+    std::shared_ptr<MotionEmuDevice> device;
+};
+
+std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
+    int update_period = params.Get("update_period", 100);
+    float sensitivity = params.Get("sensitivity", 0.01f);
+    auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
+    // Previously created device is disconnected here. Having two motion devices for 3DS is not
+    // expected.
+    current_device = device_wrapper->device;
+    return std::move(device_wrapper);
+}
+
+void MotionEmu::BeginTilt(int x, int y) {
+    if (auto ptr = current_device.lock()) {
+        ptr->BeginTilt(x, y);
+    }
+}
+
+void MotionEmu::Tilt(int x, int y) {
+    if (auto ptr = current_device.lock()) {
+        ptr->Tilt(x, y);
+    }
+}
+
+void MotionEmu::EndTilt() {
+    if (auto ptr = current_device.lock()) {
+        ptr->EndTilt();
+    }
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/motion_emu.h b/src/input_common/motion_emu.h
new file mode 100644
index 000000000..7a7e22467
--- /dev/null
+++ b/src/input_common/motion_emu.h
@@ -0,0 +1,46 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/frontend/input.h"
+
+namespace InputCommon {
+
+class MotionEmuDevice;
+
+class MotionEmu : public Input::Factory<Input::MotionDevice> {
+public:
+    /**
+     * Creates a motion device emulated from mouse input
+     * @param params contains parameters for creating the device:
+     *     - "update_period": update period in milliseconds
+     *     - "sensitivity": the coefficient converting mouse movement to tilting angle
+     */
+    std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
+
+    /**
+     * Signals that a motion sensor tilt has begun.
+     * @param x the x-coordinate of the cursor
+     * @param y the y-coordinate of the cursor
+     */
+    void BeginTilt(int x, int y);
+
+    /**
+     * Signals that a motion sensor tilt is occurring.
+     * @param x the x-coordinate of the cursor
+     * @param y the y-coordinate of the cursor
+     */
+    void Tilt(int x, int y);
+
+    /**
+     * Signals that a motion sensor tilt has ended.
+     */
+    void EndTilt();
+
+private:
+    std::weak_ptr<MotionEmuDevice> current_device;
+};
+
+} // namespace InputCommon