From f09a023292e659af46d551b9b134d94d000a57c7 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 20:27:34 -0600
Subject: input_common: Add support for joycon input reports

---
 .../helpers/joycon_protocol/poller.cpp             | 315 +++++++++++++++++++++
 1 file changed, 315 insertions(+)
 create mode 100644 src/input_common/helpers/joycon_protocol/poller.cpp

(limited to 'src/input_common/helpers/joycon_protocol/poller.cpp')

diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
new file mode 100644
index 0000000000..341479c0ca
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -0,0 +1,315 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/poller.h"
+
+namespace InputCommon::Joycon {
+
+JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
+                           JoyStickCalibration right_stick_calibration_,
+                           MotionCalibration motion_calibration_)
+    : device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
+      right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
+
+void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
+    callbacks = std::move(callbacks_);
+}
+
+void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status) {
+    InputReportActive data{};
+    memcpy(&data, buffer.data(), sizeof(InputReportActive));
+
+    switch (device_type) {
+    case Joycon::ControllerType::Left:
+        UpdateActiveLeftPadInput(data, motion_status);
+        break;
+    case Joycon::ControllerType::Right:
+        UpdateActiveRightPadInput(data, motion_status);
+        break;
+    case Joycon::ControllerType::Pro:
+        UpdateActiveProPadInput(data, motion_status);
+        break;
+    case Joycon::ControllerType::Grip:
+    case Joycon::ControllerType::Dual:
+    case Joycon::ControllerType::None:
+        break;
+    }
+
+    callbacks.on_battery_data(data.battery_status);
+}
+
+void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
+    InputReportPassive data{};
+    memcpy(&data, buffer.data(), sizeof(InputReportPassive));
+
+    switch (device_type) {
+    case Joycon::ControllerType::Left:
+        UpdatePasiveLeftPadInput(data);
+        break;
+    case Joycon::ControllerType::Right:
+        UpdatePasiveRightPadInput(data);
+        break;
+    case Joycon::ControllerType::Pro:
+        UpdatePasiveProPadInput(data);
+        break;
+    case Joycon::ControllerType::Grip:
+    case Joycon::ControllerType::Dual:
+    case Joycon::ControllerType::None:
+        break;
+    }
+}
+
+void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
+    // This mode is compatible with the active mode
+    ReadActiveMode(buffer, motion_status);
+}
+
+void JoyconPoller::UpdateColor(const Color& color) {
+    callbacks.on_color_data(color);
+}
+
+void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
+                                            const MotionStatus& motion_status) {
+    static constexpr std::array<Joycon::PadButton, 11> left_buttons{
+        Joycon::PadButton::Down,    Joycon::PadButton::Up,     Joycon::PadButton::Right,
+        Joycon::PadButton::Left,    Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
+        Joycon::PadButton::L,       Joycon::PadButton::ZL,     Joycon::PadButton::Minus,
+        Joycon::PadButton::Capture, Joycon::PadButton::StickL,
+    };
+
+    const u32 raw_button =
+        static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
+    for (std::size_t i = 0; i < left_buttons.size(); ++i) {
+        const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
+        const int button = static_cast<int>(left_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+
+    const u16 raw_left_axis_x =
+        static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
+    const u16 raw_left_axis_y =
+        static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
+    const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
+    const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
+
+    if (motion_status.is_enabled) {
+        auto left_motion = GetMotionInput(input, motion_status);
+        // Rotate motion axis to the correct direction
+        left_motion.accel_y = -left_motion.accel_y;
+        left_motion.accel_z = -left_motion.accel_z;
+        left_motion.gyro_x = -left_motion.gyro_x;
+        callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
+    }
+}
+
+void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
+                                             const MotionStatus& motion_status) {
+    static constexpr std::array<Joycon::PadButton, 11> right_buttons{
+        Joycon::PadButton::Y,    Joycon::PadButton::X,       Joycon::PadButton::B,
+        Joycon::PadButton::A,    Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
+        Joycon::PadButton::R,    Joycon::PadButton::ZR,      Joycon::PadButton::Plus,
+        Joycon::PadButton::Home, Joycon::PadButton::StickR,
+    };
+
+    const u32 raw_button =
+        static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
+    for (std::size_t i = 0; i < right_buttons.size(); ++i) {
+        const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
+        const int button = static_cast<int>(right_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+
+    const u16 raw_right_axis_x =
+        static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
+    const u16 raw_right_axis_y =
+        static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
+    const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
+    const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
+
+    if (motion_status.is_enabled) {
+        auto right_motion = GetMotionInput(input, motion_status);
+        // Rotate motion axis to the correct direction
+        right_motion.accel_x = -right_motion.accel_x;
+        right_motion.accel_y = -right_motion.accel_y;
+        right_motion.gyro_z = -right_motion.gyro_z;
+        callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
+    }
+}
+
+void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
+                                           const MotionStatus& motion_status) {
+    static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
+        Joycon::PadButton::Down,  Joycon::PadButton::Up,      Joycon::PadButton::Right,
+        Joycon::PadButton::Left,  Joycon::PadButton::L,       Joycon::PadButton::ZL,
+        Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
+        Joycon::PadButton::X,     Joycon::PadButton::B,       Joycon::PadButton::A,
+        Joycon::PadButton::R,     Joycon::PadButton::ZR,      Joycon::PadButton::Plus,
+        Joycon::PadButton::Home,  Joycon::PadButton::StickL,  Joycon::PadButton::StickR,
+    };
+
+    const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
+                                            (input.button_input[1] << 16));
+    for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
+        const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
+        const int button = static_cast<int>(pro_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+
+    const u16 raw_left_axis_x =
+        static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
+    const u16 raw_left_axis_y =
+        static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
+    const u16 raw_right_axis_x =
+        static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
+    const u16 raw_right_axis_y =
+        static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
+
+    const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
+    const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
+    const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
+    const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
+    callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
+
+    if (motion_status.is_enabled) {
+        auto pro_motion = GetMotionInput(input, motion_status);
+        pro_motion.gyro_x = -pro_motion.gyro_x;
+        pro_motion.accel_y = -pro_motion.accel_y;
+        pro_motion.accel_z = -pro_motion.accel_z;
+        callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
+        callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
+    }
+}
+
+void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
+    static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
+        Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
+        Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
+        Joycon::PasivePadButton::SL,     Joycon::PasivePadButton::SR,
+        Joycon::PasivePadButton::L_R,    Joycon::PasivePadButton::ZL_ZR,
+        Joycon::PasivePadButton::Minus,  Joycon::PasivePadButton::Capture,
+        Joycon::PasivePadButton::StickL,
+    };
+
+    for (std::size_t i = 0; i < left_buttons.size(); ++i) {
+        const bool button_status = (input.button_input & static_cast<u32>(left_buttons[i])) != 0;
+        const int button = static_cast<int>(left_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+}
+
+void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
+    static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
+        Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
+        Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
+        Joycon::PasivePadButton::SL,     Joycon::PasivePadButton::SR,
+        Joycon::PasivePadButton::L_R,    Joycon::PasivePadButton::ZL_ZR,
+        Joycon::PasivePadButton::Plus,   Joycon::PasivePadButton::Home,
+        Joycon::PasivePadButton::StickR,
+    };
+
+    for (std::size_t i = 0; i < right_buttons.size(); ++i) {
+        const bool button_status = (input.button_input & static_cast<u32>(right_buttons[i])) != 0;
+        const int button = static_cast<int>(right_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+}
+
+void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
+    static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
+        Joycon::PasivePadButton::Down_A,  Joycon::PasivePadButton::Right_X,
+        Joycon::PasivePadButton::Left_B,  Joycon::PasivePadButton::Up_Y,
+        Joycon::PasivePadButton::SL,      Joycon::PasivePadButton::SR,
+        Joycon::PasivePadButton::L_R,     Joycon::PasivePadButton::ZL_ZR,
+        Joycon::PasivePadButton::Minus,   Joycon::PasivePadButton::Plus,
+        Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
+        Joycon::PasivePadButton::StickL,  Joycon::PasivePadButton::StickR,
+    };
+
+    for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
+        const bool button_status = (input.button_input & static_cast<u32>(pro_buttons[i])) != 0;
+        const int button = static_cast<int>(pro_buttons[i]);
+        callbacks.on_button_data(button, button_status);
+    }
+}
+
+f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
+    const f32 value = static_cast<f32>(raw_value - calibration.center);
+    if (value > 0.0f) {
+        return value / calibration.max;
+    }
+    return value / calibration.min;
+}
+
+f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
+                                        AccelerometerSensitivity sensitivity) const {
+    const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
+    switch (sensitivity) {
+    case Joycon::AccelerometerSensitivity::G2:
+        return value / 4.0f;
+    case Joycon::AccelerometerSensitivity::G4:
+        return value / 2.0f;
+    case Joycon::AccelerometerSensitivity::G8:
+        return value;
+    case Joycon::AccelerometerSensitivity::G16:
+        return value * 2.0f;
+    }
+    return value;
+}
+
+f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
+                               GyroSensitivity sensitivity) const {
+    const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
+    switch (sensitivity) {
+    case Joycon::GyroSensitivity::DPS250:
+        return value / 8.0f;
+    case Joycon::GyroSensitivity::DPS500:
+        return value / 4.0f;
+    case Joycon::GyroSensitivity::DPS1000:
+        return value / 2.0f;
+    case Joycon::GyroSensitivity::DPS2000:
+        return value;
+    }
+    return value;
+}
+
+s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
+                                  const InputReportActive& input) const {
+    return input.motion_input[(sensor * 3) + axis];
+}
+
+MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
+                                        const MotionStatus& motion_status) const {
+    MotionData motion{};
+    const auto& accel_cal = motion_calibration.accelerometer;
+    const auto& gyro_cal = motion_calibration.gyro;
+    const s16 raw_accel_x = input.motion_input[1];
+    const s16 raw_accel_y = input.motion_input[0];
+    const s16 raw_accel_z = input.motion_input[2];
+    const s16 raw_gyro_x = input.motion_input[4];
+    const s16 raw_gyro_y = input.motion_input[3];
+    const s16 raw_gyro_z = input.motion_input[5];
+
+    motion.delta_timestamp = motion_status.delta_time;
+    motion.accel_x =
+        GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
+    motion.accel_y =
+        GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
+    motion.accel_z =
+        GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
+    motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
+    motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
+    motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
+
+    // TODO(German77): Return all three samples data
+    return motion;
+}
+
+} // namespace InputCommon::Joycon
-- 
cgit v1.2.3-70-g09d2


From 751d36e7392b0b1637f17988cfc1ef0d7cd95753 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 19:10:42 -0600
Subject: input_common: Add support for joycon ring controller

---
 src/input_common/CMakeLists.txt                    |   2 +
 src/input_common/helpers/joycon_driver.cpp         |  43 ++++++-
 src/input_common/helpers/joycon_driver.h           |   3 +
 .../helpers/joycon_protocol/calibration.cpp        |  22 ++++
 .../helpers/joycon_protocol/calibration.h          |  10 ++
 .../helpers/joycon_protocol/poller.cpp             |  22 +++-
 src/input_common/helpers/joycon_protocol/poller.h  |   4 +-
 .../helpers/joycon_protocol/ringcon.cpp            | 132 +++++++++++++++++++++
 src/input_common/helpers/joycon_protocol/ringcon.h |  38 ++++++
 9 files changed, 272 insertions(+), 4 deletions(-)
 create mode 100644 src/input_common/helpers/joycon_protocol/ringcon.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/ringcon.h

(limited to 'src/input_common/helpers/joycon_protocol/poller.cpp')

diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 4ab1ccbfbe..addecc9b3b 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -66,6 +66,8 @@ if (ENABLE_SDL2)
         helpers/joycon_protocol/joycon_types.h
         helpers/joycon_protocol/poller.cpp
         helpers/joycon_protocol/poller.h
+        helpers/joycon_protocol/ringcon.cpp
+        helpers/joycon_protocol/ringcon.h
         helpers/joycon_protocol/rumble.cpp
         helpers/joycon_protocol/rumble.h
     )
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 5d0aeabf5f..c0a03fe2e1 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -52,12 +52,18 @@ DriverResult JoyconDriver::InitializeDevice() {
     error_counter = 0;
     hidapi_handle->packet_counter = 0;
 
+    // Reset external device status
+    starlink_connected = false;
+    ring_connected = false;
+    amiibo_detected = false;
+
     // Set HW default configuration
     vibration_enabled = true;
     motion_enabled = true;
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = false;
+    irs_enabled = false;
     gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
     gyro_performance = Joycon::GyroPerformance::HZ833;
     accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
@@ -66,6 +72,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     // Initialize HW Protocols
     calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
+    ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
     rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
 
     // Get fixed joycon info
@@ -172,9 +179,23 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
         .accelerometer_sensitivity = accelerometer_sensitivity,
     };
 
+    // TODO: Remove this when calibration is properly loaded and not calculated
+    if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) {
+        InputReportActive data{};
+        memcpy(&data, buffer.data(), sizeof(InputReportActive));
+        calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
+    }
+
+    const RingStatus ring_status{
+        .is_enabled = ring_connected,
+        .default_value = ring_calibration.default_value,
+        .max_value = ring_calibration.max_value,
+        .min_value = ring_calibration.min_value,
+    };
+
     switch (report_mode) {
     case InputReport::STANDARD_FULL_60HZ:
-        joycon_poller->ReadActiveMode(buffer, motion_status);
+        joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
         break;
     case InputReport::NFC_IR_MODE_60HZ:
         joycon_poller->ReadNfcIRMode(buffer, motion_status);
@@ -204,6 +225,26 @@ void JoyconDriver::SetPollingMode() {
         generic_protocol->EnableImu(false);
     }
 
+    if (ring_protocol->IsEnabled()) {
+        ring_connected = false;
+        ring_protocol->DisableRingCon();
+    }
+
+    if (hidbus_enabled && supported_features.hidbus) {
+        auto result = ring_protocol->EnableRingCon();
+        if (result == DriverResult::Success) {
+            result = ring_protocol->StartRingconPolling();
+        }
+        if (result == DriverResult::Success) {
+            ring_connected = true;
+            disable_input_thread = false;
+            return;
+        }
+        ring_connected = false;
+        ring_protocol->DisableRingCon();
+        LOG_ERROR(Input, "Error enabling Ringcon");
+    }
+
     if (passive_enabled && supported_features.passive) {
         const auto result = generic_protocol->EnablePassiveMode();
         if (result == DriverResult::Success) {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 48ba859f4e..dc5d60221a 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -12,6 +12,7 @@
 #include "input_common/helpers/joycon_protocol/generic_functions.h"
 #include "input_common/helpers/joycon_protocol/joycon_types.h"
 #include "input_common/helpers/joycon_protocol/poller.h"
+#include "input_common/helpers/joycon_protocol/ringcon.h"
 #include "input_common/helpers/joycon_protocol/rumble.h"
 
 namespace InputCommon::Joycon {
@@ -86,6 +87,7 @@ private:
     std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr;
     std::unique_ptr<GenericProtocol> generic_protocol = nullptr;
     std::unique_ptr<JoyconPoller> joycon_poller = nullptr;
+    std::unique_ptr<RingConProtocol> ring_protocol = nullptr;
     std::unique_ptr<RumbleProtocol> rumble_protocol = nullptr;
 
     // Connection status
@@ -118,6 +120,7 @@ private:
     JoyStickCalibration left_stick_calibration{};
     JoyStickCalibration right_stick_calibration{};
     MotionCalibration motion_calibration{};
+    RingCalibration ring_calibration{};
 
     // Fixed joycon info
     FirmwareVersion version{};
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
index 5c29af545b..ce1ff7061d 100644
--- a/src/input_common/helpers/joycon_protocol/calibration.cpp
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -128,6 +128,28 @@ DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibrati
     return result;
 }
 
+DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
+                                                     s16 current_value) {
+    // TODO: Get default calibration form ring itself
+    if (ring_data_max == 0 && ring_data_min == 0) {
+        ring_data_max = current_value + 800;
+        ring_data_min = current_value - 800;
+        ring_data_default = current_value;
+    }
+    if (ring_data_max < current_value) {
+        ring_data_max = current_value;
+    }
+    if (ring_data_min > current_value) {
+        ring_data_min = current_value;
+    }
+    calibration = {
+        .default_value = ring_data_default,
+        .max_value = ring_data_max,
+        .min_value = ring_data_min,
+    };
+    return DriverResult::Success;
+}
+
 void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
     constexpr u16 DefaultStickCenter{2048};
     constexpr u16 DefaultStickRange{1740};
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h
index 38214eed41..32ddef4b85 100644
--- a/src/input_common/helpers/joycon_protocol/calibration.h
+++ b/src/input_common/helpers/joycon_protocol/calibration.h
@@ -46,9 +46,19 @@ public:
      */
     DriverResult GetImuCalibration(MotionCalibration& calibration);
 
+    /**
+     * Calculates on run time the proper calibration of the ring controller
+     * @returns RingCalibration of the ring sensor
+     */
+    DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
+
 private:
     void ValidateCalibration(JoyStickCalibration& calibration);
     void ValidateCalibration(MotionCalibration& calibration);
+
+    s16 ring_data_max = 0;
+    s16 ring_data_default = 0;
+    s16 ring_data_min = 0;
 };
 
 } // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index 341479c0ca..cb76e1e06f 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -16,7 +16,8 @@ void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
     callbacks = std::move(callbacks_);
 }
 
-void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status) {
+void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
+                                  const RingStatus& ring_status) {
     InputReportActive data{};
     memcpy(&data, buffer.data(), sizeof(InputReportActive));
 
@@ -36,6 +37,10 @@ void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& moti
         break;
     }
 
+    if (ring_status.is_enabled) {
+        UpdateRing(data.ring_input, ring_status);
+    }
+
     callbacks.on_battery_data(data.battery_status);
 }
 
@@ -62,13 +67,26 @@ void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
 
 void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
     // This mode is compatible with the active mode
-    ReadActiveMode(buffer, motion_status);
+    ReadActiveMode(buffer, motion_status, {});
 }
 
 void JoyconPoller::UpdateColor(const Color& color) {
     callbacks.on_color_data(color);
 }
 
+void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
+    float normalized_value = static_cast<float>(value - ring_status.default_value);
+    if (normalized_value > 0) {
+        normalized_value = normalized_value /
+                           static_cast<float>(ring_status.max_value - ring_status.default_value);
+    }
+    if (normalized_value < 0) {
+        normalized_value = normalized_value /
+                           static_cast<float>(ring_status.default_value - ring_status.min_value);
+    }
+    callbacks.on_ring_data(normalized_value);
+}
+
 void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
                                             const MotionStatus& motion_status) {
     static constexpr std::array<Joycon::PadButton, 11> left_buttons{
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
index fff681d0a1..68b14b8ba0 100644
--- a/src/input_common/helpers/joycon_protocol/poller.h
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -28,12 +28,14 @@ public:
     void ReadPassiveMode(std::span<u8> buffer);
 
     /// Handles data from active packages
-    void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status);
+    void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
+                        const RingStatus& ring_status);
 
     /// Handles data from nfc or ir packages
     void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
 
     void UpdateColor(const Color& color);
+    void UpdateRing(s16 value, const RingStatus& ring_status);
 
 private:
     void UpdateActiveLeftPadInput(const InputReportActive& input,
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
new file mode 100644
index 0000000000..2d137b85d3
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -0,0 +1,132 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/ringcon.h"
+
+namespace InputCommon::Joycon {
+
+RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
+    : JoyconCommonProtocol(handle) {}
+
+DriverResult RingConProtocol::EnableRingCon() {
+    LOG_DEBUG(Input, "Enable Ringcon");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
+    }
+    if (result == DriverResult::Success) {
+        result = EnableMCU(true);
+    }
+    if (result == DriverResult::Success) {
+        const MCUConfig config{
+            .command = MCUCommand::ConfigureMCU,
+            .sub_command = MCUSubCommand::SetDeviceMode,
+            .mode = MCUMode::Standby,
+            .crc = {},
+        };
+        result = ConfigureMCU(config);
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult RingConProtocol::DisableRingCon() {
+    LOG_DEBUG(Input, "Disable RingCon");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = EnableMCU(false);
+    }
+
+    is_enabled = false;
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult RingConProtocol::StartRingconPolling() {
+    LOG_DEBUG(Input, "Enable Ringcon");
+    bool is_connected = false;
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::STANDARD_FULL_60HZ, MCUMode::Standby);
+    }
+    if (result == DriverResult::Success) {
+        result = IsRingConnected(is_connected);
+    }
+    if (result == DriverResult::Success && is_connected) {
+        LOG_INFO(Input, "Ringcon detected");
+        result = ConfigureRing();
+    }
+    if (result == DriverResult::Success) {
+        is_enabled = true;
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
+    LOG_DEBUG(Input, "IsRingConnected");
+    constexpr std::size_t max_tries = 28;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+    is_connected = false;
+
+    do {
+        std::vector<u8> empty_data(0);
+        const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+
+        if (tries++ >= max_tries) {
+            return DriverResult::NoDeviceDetected;
+        }
+    } while (output[14] != 0x59 || output[16] != 0x20);
+
+    is_connected = true;
+    return DriverResult::Success;
+}
+
+DriverResult RingConProtocol::ConfigureRing() {
+    LOG_DEBUG(Input, "ConfigureRing");
+    constexpr std::size_t max_tries = 28;
+    DriverResult result{DriverResult::Success};
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    do {
+        std::vector<u8> ring_config{0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16,
+                                    0xED, 0x34, 0x36, 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6,
+                                    0xA9, 0x22, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                    0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
+        result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ >= max_tries) {
+            return DriverResult::NoDeviceDetected;
+        }
+    } while (output[14] != 0x5C);
+
+    std::vector<u8> ringcon_data{0x04, 0x01, 0x01, 0x02};
+    result = SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data, output);
+
+    return result;
+}
+
+bool RingConProtocol::IsEnabled() {
+    return is_enabled;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h
new file mode 100644
index 0000000000..0c25de23ee
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.h
@@ -0,0 +1,38 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <vector>
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+class RingConProtocol final : private JoyconCommonProtocol {
+public:
+    RingConProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    DriverResult EnableRingCon();
+
+    DriverResult DisableRingCon();
+
+    DriverResult StartRingconPolling();
+
+    bool IsEnabled();
+
+private:
+    DriverResult IsRingConnected(bool& is_connected);
+
+    DriverResult ConfigureRing();
+
+    bool is_enabled{};
+};
+
+} // namespace InputCommon::Joycon
-- 
cgit v1.2.3-70-g09d2


From 6d6b7bdbc327528d155f0422ef096846559844c0 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Thu, 22 Dec 2022 01:07:46 -0600
Subject: input_common: Implement joycon nfc

---
 src/core/hid/emulated_controller.cpp               |   3 +-
 src/input_common/CMakeLists.txt                    |   2 +
 src/input_common/drivers/joycon.cpp                |   4 +-
 src/input_common/helpers/joycon_driver.cpp         |  44 +++
 src/input_common/helpers/joycon_driver.h           |  24 +-
 src/input_common/helpers/joycon_protocol/nfc.cpp   | 414 +++++++++++++++++++++
 src/input_common/helpers/joycon_protocol/nfc.h     |  61 +++
 .../helpers/joycon_protocol/poller.cpp             |   4 +
 src/input_common/helpers/joycon_protocol/poller.h  |   1 +
 9 files changed, 544 insertions(+), 13 deletions(-)
 create mode 100644 src/input_common/helpers/joycon_protocol/nfc.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/nfc.h

(limited to 'src/input_common/helpers/joycon_protocol/poller.cpp')

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 89638cb856..1e4ec4addc 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -144,7 +144,8 @@ void EmulatedController::LoadDevices() {
     battery_params[RightIndex].Set("battery", true);
 
     camera_params = Common::ParamPackage{"engine:camera,camera:1"};
-    nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
+    nfc_params = right_joycon;
+    nfc_params.Set("nfc", true);
     ring_params = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
 
     output_params[LeftIndex] = left_joycon;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index addecc9b3b..9c901af2ab 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -64,6 +64,8 @@ if (ENABLE_SDL2)
         helpers/joycon_protocol/generic_functions.cpp
         helpers/joycon_protocol/generic_functions.h
         helpers/joycon_protocol/joycon_types.h
+        helpers/joycon_protocol/nfc.cpp
+        helpers/joycon_protocol/nfc.h
         helpers/joycon_protocol/poller.cpp
         helpers/joycon_protocol/poller.h
         helpers/joycon_protocol/ringcon.cpp
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index 049ecc4f24..29f0dc0c89 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -388,7 +388,9 @@ void Joycons::OnRingConUpdate(f32 ring_data) {
 
 void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
     const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
-    SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
+    const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
+                                               : Common::Input::NfcState::NewAmiibo;
+    SetNfc(identifier, {nfc_state, amiibo_data});
 }
 
 std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index c0a03fe2e1..c3debffd11 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -5,6 +5,12 @@
 #include "common/swap.h"
 #include "common/thread.h"
 #include "input_common/helpers/joycon_driver.h"
+#include "input_common/helpers/joycon_protocol/calibration.h"
+#include "input_common/helpers/joycon_protocol/generic_functions.h"
+#include "input_common/helpers/joycon_protocol/nfc.h"
+#include "input_common/helpers/joycon_protocol/poller.h"
+#include "input_common/helpers/joycon_protocol/ringcon.h"
+#include "input_common/helpers/joycon_protocol/rumble.h"
 
 namespace InputCommon::Joycon {
 JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
@@ -72,6 +78,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     // Initialize HW Protocols
     calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
+    nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
     ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
     rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
 
@@ -193,6 +200,25 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
         .min_value = ring_calibration.min_value,
     };
 
+    if (nfc_protocol->IsEnabled()) {
+        if (amiibo_detected) {
+            if (!nfc_protocol->HasAmiibo()) {
+                joycon_poller->updateAmiibo({});
+                amiibo_detected = false;
+                return;
+            }
+        }
+
+        if (!amiibo_detected) {
+            std::vector<u8> data(0x21C);
+            const auto result = nfc_protocol->ScanAmiibo(data);
+            if (result == DriverResult::Success) {
+                joycon_poller->updateAmiibo(data);
+                amiibo_detected = true;
+            }
+        }
+    }
+
     switch (report_mode) {
     case InputReport::STANDARD_FULL_60HZ:
         joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
@@ -225,6 +251,24 @@ void JoyconDriver::SetPollingMode() {
         generic_protocol->EnableImu(false);
     }
 
+    if (nfc_protocol->IsEnabled()) {
+        amiibo_detected = false;
+        nfc_protocol->DisableNfc();
+    }
+
+    if (nfc_enabled && supported_features.nfc) {
+        auto result = nfc_protocol->EnableNfc();
+        if (result == DriverResult::Success) {
+            result = nfc_protocol->StartNFCPollingMode();
+        }
+        if (result == DriverResult::Success) {
+            disable_input_thread = false;
+            return;
+        }
+        nfc_protocol->DisableNfc();
+        LOG_ERROR(Input, "Error enabling NFC");
+    }
+
     if (ring_protocol->IsEnabled()) {
         ring_connected = false;
         ring_protocol->DisableRingCon();
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index dc5d60221a..c9118ee939 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -8,14 +8,15 @@
 #include <span>
 #include <thread>
 
-#include "input_common/helpers/joycon_protocol/calibration.h"
-#include "input_common/helpers/joycon_protocol/generic_functions.h"
 #include "input_common/helpers/joycon_protocol/joycon_types.h"
-#include "input_common/helpers/joycon_protocol/poller.h"
-#include "input_common/helpers/joycon_protocol/ringcon.h"
-#include "input_common/helpers/joycon_protocol/rumble.h"
 
 namespace InputCommon::Joycon {
+class CalibrationProtocol;
+class GenericProtocol;
+class NfcProtocol;
+class JoyconPoller;
+class RingConProtocol;
+class RumbleProtocol;
 
 class JoyconDriver final {
 public:
@@ -84,17 +85,18 @@ private:
     SupportedFeatures GetSupportedFeatures();
 
     // Protocol Features
-    std::unique_ptr<CalibrationProtocol> calibration_protocol = nullptr;
-    std::unique_ptr<GenericProtocol> generic_protocol = nullptr;
-    std::unique_ptr<JoyconPoller> joycon_poller = nullptr;
-    std::unique_ptr<RingConProtocol> ring_protocol = nullptr;
-    std::unique_ptr<RumbleProtocol> rumble_protocol = nullptr;
+    std::unique_ptr<CalibrationProtocol> calibration_protocol;
+    std::unique_ptr<GenericProtocol> generic_protocol;
+    std::unique_ptr<NfcProtocol> nfc_protocol;
+    std::unique_ptr<JoyconPoller> joycon_poller;
+    std::unique_ptr<RingConProtocol> ring_protocol;
+    std::unique_ptr<RumbleProtocol> rumble_protocol;
 
     // Connection status
     bool is_connected{};
     u64 delta_time;
     std::size_t error_counter{};
-    std::shared_ptr<JoyconHandle> hidapi_handle = nullptr;
+    std::shared_ptr<JoyconHandle> hidapi_handle;
     std::chrono::time_point<std::chrono::steady_clock> last_update;
 
     // External device status
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
new file mode 100644
index 0000000000..69b2bfe050
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -0,0 +1,414 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <thread>
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/nfc.h"
+
+namespace InputCommon::Joycon {
+
+NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle) : JoyconCommonProtocol(handle) {}
+
+DriverResult NfcProtocol::EnableNfc() {
+    LOG_INFO(Input, "Enable NFC");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
+    }
+    if (result == DriverResult::Success) {
+        result = EnableMCU(true);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
+    }
+    if (result == DriverResult::Success) {
+        const MCUConfig config{
+            .command = MCUCommand::ConfigureMCU,
+            .sub_command = MCUSubCommand::SetMCUMode,
+            .mode = MCUMode::NFC,
+            .crc = {},
+        };
+
+        result = ConfigureMCU(config);
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult NfcProtocol::DisableNfc() {
+    LOG_DEBUG(Input, "Disable NFC");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = EnableMCU(false);
+    }
+
+    is_enabled = false;
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult NfcProtocol::StartNFCPollingMode() {
+    LOG_DEBUG(Input, "Start NFC pooling Mode");
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIsReady();
+    }
+    if (result == DriverResult::Success) {
+        is_enabled = true;
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
+    LOG_DEBUG(Input, "Start NFC pooling Mode");
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = StartPolling(tag_data);
+    }
+    if (result == DriverResult::Success) {
+        result = ReadTag(tag_data);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIsReady();
+    }
+    if (result == DriverResult::Success) {
+        result = StartPolling(tag_data);
+    }
+    if (result == DriverResult::Success) {
+        result = GetAmiiboData(data);
+    }
+
+    SetNonBlocking();
+    return result;
+}
+
+bool NfcProtocol::HasAmiibo() {
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = StartPolling(tag_data);
+    }
+
+    SetNonBlocking();
+    return result == DriverResult::Success;
+}
+
+DriverResult NfcProtocol::WaitUntilNfcIsReady() {
+    constexpr std::size_t timeout_limit = 10;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    do {
+        auto result = SendStartWaitingRecieveRequest(output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ > timeout_limit) {
+            return DriverResult::Timeout;
+        }
+    } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 ||
+             output[56] != 0x00);
+
+    return DriverResult::Success;
+}
+
+DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
+    LOG_DEBUG(Input, "Start Polling for tag");
+    constexpr std::size_t timeout_limit = 7;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    do {
+        const auto result = SendStartPollingRequest(output);
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ > timeout_limit) {
+            return DriverResult::Timeout;
+        }
+    } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09);
+
+    data.type = output[62];
+    data.uuid.resize(output[64]);
+    memcpy(data.uuid.data(), output.data() + 65, data.uuid.size());
+
+    return DriverResult::Success;
+}
+
+DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
+    constexpr std::size_t timeout_limit = 10;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    std::string uuid_string = "";
+    for (auto& content : data.uuid) {
+        uuid_string += " " + fmt::format("{:02x}", content);
+    }
+
+    LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
+
+    tries = 0;
+    std::size_t ntag_pages = 0;
+    // Read Tag data
+loop1:
+    while (true) {
+        auto result = SendReadAmiiboRequest(output, ntag_pages);
+
+        int attempt = 0;
+        while (1) {
+            if (attempt != 0) {
+                result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
+            }
+            if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
+                return DriverResult::ErrorReadingData;
+            }
+            if (output[49] == 0x3a && output[51] == 0x07 && output[52] == 0x01) {
+                if (data.type != 2) {
+                    goto loop1;
+                }
+                switch (output[74]) {
+                case 0:
+                    ntag_pages = 135;
+                    break;
+                case 3:
+                    ntag_pages = 45;
+                    break;
+                case 4:
+                    ntag_pages = 231;
+                    break;
+                default:
+                    return DriverResult::ErrorReadingData;
+                }
+                goto loop1;
+            }
+            if (output[49] == 0x2a && output[56] == 0x04) {
+                // finished
+                SendStopPollingRequest(output);
+                return DriverResult::Success;
+            }
+            if (output[49] == 0x2a) {
+                goto loop1;
+            }
+            if (attempt++ > 6) {
+                goto loop1;
+            }
+        }
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ > timeout_limit) {
+            return DriverResult::Timeout;
+        }
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
+    constexpr std::size_t timeout_limit = 10;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    std::size_t ntag_pages = 135;
+    std::size_t ntag_buffer_pos = 0;
+    // Read Tag data
+loop1:
+    while (true) {
+        auto result = SendReadAmiiboRequest(output, ntag_pages);
+
+        int attempt = 0;
+        while (1) {
+            if (attempt != 0) {
+                result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
+            }
+            if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
+                return DriverResult::ErrorReadingData;
+            }
+            if (output[49] == 0x3a && output[51] == 0x07) {
+                std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
+                if (output[52] == 0x01) {
+                    memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116,
+                           payload_size - 60);
+                    ntag_buffer_pos += payload_size - 60;
+                } else {
+                    memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
+                }
+                goto loop1;
+            }
+            if (output[49] == 0x2a && output[56] == 0x04) {
+                LOG_INFO(Input, "Finished reading amiibo");
+                return DriverResult::Success;
+            }
+            if (output[49] == 0x2a) {
+                goto loop1;
+            }
+            if (attempt++ > 4) {
+                goto loop1;
+            }
+        }
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ > timeout_limit) {
+            return DriverResult::Timeout;
+        }
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
+    NFCRequestState request{
+        .sub_command = MCUSubCommand::ReadDeviceMode,
+        .command_argument = NFCReadCommand::StartPolling,
+        .packet_id = 0x0,
+        .packet_flag = MCUPacketFlag::LastCommandPacket,
+        .data_length = sizeof(NFCPollingCommandData),
+        .nfc_polling =
+            {
+                .enable_mifare = 0x01,
+                .unknown_1 = 0x00,
+                .unknown_2 = 0x00,
+                .unknown_3 = 0x2c,
+                .unknown_4 = 0x01,
+            },
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(NFCRequestState));
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+}
+
+DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
+    NFCRequestState request{
+        .sub_command = MCUSubCommand::ReadDeviceMode,
+        .command_argument = NFCReadCommand::StopPolling,
+        .packet_id = 0x0,
+        .packet_flag = MCUPacketFlag::LastCommandPacket,
+        .data_length = 0,
+        .raw_data = {},
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(NFCRequestState));
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+}
+
+DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) {
+    NFCRequestState request{
+        .sub_command = MCUSubCommand::ReadDeviceMode,
+        .command_argument = NFCReadCommand::StartWaitingRecieve,
+        .packet_id = 0x0,
+        .packet_flag = MCUPacketFlag::LastCommandPacket,
+        .data_length = 0,
+        .raw_data = {},
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(NFCRequestState));
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+}
+
+DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages) {
+    NFCRequestState request{
+        .sub_command = MCUSubCommand::ReadDeviceMode,
+        .command_argument = NFCReadCommand::Ntag,
+        .packet_id = 0x0,
+        .packet_flag = MCUPacketFlag::LastCommandPacket,
+        .data_length = sizeof(NFCReadCommandData),
+        .nfc_read =
+            {
+                .unknown = 0xd0,
+                .uuid_length = 0x07,
+                .unknown_2 = 0x00,
+                .uid = {},
+                .tag_type = NFCTagType::AllTags,
+                .read_block = GetReadBlockCommand(ntag_pages),
+            },
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(NFCRequestState));
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+}
+
+NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
+    if (pages == 0) {
+        return {
+            .block_count = 1,
+        };
+    }
+
+    if (pages == 45) {
+        return {
+            .block_count = 1,
+            .blocks =
+                {
+                    NFCReadBlock{0x00, 0x2C},
+                },
+        };
+    }
+
+    if (pages == 135) {
+        return {
+            .block_count = 3,
+            .blocks =
+                {
+                    NFCReadBlock{0x00, 0x3b},
+                    {0x3c, 0x77},
+                    {0x78, 0x86},
+                },
+        };
+    }
+
+    if (pages == 231) {
+        return {
+            .block_count = 4,
+            .blocks =
+                {
+                    NFCReadBlock{0x00, 0x3b},
+                    {0x3c, 0x77},
+                    {0x78, 0x83},
+                    {0xb4, 0xe6},
+                },
+        };
+    }
+
+    return {};
+}
+
+bool NfcProtocol::IsEnabled() {
+    return is_enabled;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
new file mode 100644
index 0000000000..0ede03d505
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -0,0 +1,61 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <vector>
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+class NfcProtocol final : private JoyconCommonProtocol {
+public:
+    NfcProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    DriverResult EnableNfc();
+
+    DriverResult DisableNfc();
+
+    DriverResult StartNFCPollingMode();
+
+    DriverResult ScanAmiibo(std::vector<u8>& data);
+
+    bool HasAmiibo();
+
+    bool IsEnabled();
+
+private:
+    struct TagFoundData {
+        u8 type;
+        std::vector<u8> uuid;
+    };
+
+    DriverResult WaitUntilNfcIsReady();
+
+    DriverResult StartPolling(TagFoundData& data);
+
+    DriverResult ReadTag(const TagFoundData& data);
+
+    DriverResult GetAmiiboData(std::vector<u8>& data);
+
+    DriverResult SendStartPollingRequest(std::vector<u8>& output);
+
+    DriverResult SendStopPollingRequest(std::vector<u8>& output);
+
+    DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
+
+    DriverResult SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages);
+
+    NFCReadBlockCommand GetReadBlockCommand(std::size_t pages) const;
+
+    bool is_enabled{};
+};
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index cb76e1e06f..fd05d98f38 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -74,6 +74,10 @@ void JoyconPoller::UpdateColor(const Color& color) {
     callbacks.on_color_data(color);
 }
 
+void JoyconPoller::updateAmiibo(const std::vector<u8>& amiibo_data) {
+    callbacks.on_amiibo_data(amiibo_data);
+}
+
 void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
     float normalized_value = static_cast<float>(value - ring_status.default_value);
     if (normalized_value > 0) {
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
index 68b14b8ba0..c40fc7bca8 100644
--- a/src/input_common/helpers/joycon_protocol/poller.h
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -36,6 +36,7 @@ public:
 
     void UpdateColor(const Color& color);
     void UpdateRing(s16 value, const RingStatus& ring_status);
+    void updateAmiibo(const std::vector<u8>& amiibo_data);
 
 private:
     void UpdateActiveLeftPadInput(const InputReportActive& input,
-- 
cgit v1.2.3-70-g09d2


From 459fb2b21337bae60194a2a99ce68c87aaed522d Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Wed, 28 Dec 2022 15:21:12 -0600
Subject: input_common: Implement joycon ir camera

---
 src/core/hid/emulated_controller.cpp               |  21 +-
 src/core/hid/emulated_controller.h                 |   5 +-
 src/core/hle/service/hid/irs.cpp                   |  11 +
 src/input_common/CMakeLists.txt                    |   2 +
 src/input_common/drivers/joycon.cpp                |  29 +-
 src/input_common/drivers/joycon.h                  |   5 +-
 src/input_common/helpers/joycon_driver.cpp         |  55 +++-
 src/input_common/helpers/joycon_driver.h           |   4 +
 .../helpers/joycon_protocol/common_protocol.cpp    |  13 +
 .../helpers/joycon_protocol/common_protocol.h      |   7 +
 src/input_common/helpers/joycon_protocol/irs.cpp   | 300 +++++++++++++++++++++
 src/input_common/helpers/joycon_protocol/irs.h     |  63 +++++
 .../helpers/joycon_protocol/joycon_types.h         | 107 +++++++-
 .../helpers/joycon_protocol/poller.cpp             |   6 +-
 src/input_common/helpers/joycon_protocol/poller.h  |   3 +-
 15 files changed, 608 insertions(+), 23 deletions(-)
 create mode 100644 src/input_common/helpers/joycon_protocol/irs.cpp
 create mode 100644 src/input_common/helpers/joycon_protocol/irs.h

(limited to 'src/input_common/helpers/joycon_protocol/poller.cpp')

diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 915ffa4906..faf9e7c4e5 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -145,7 +145,9 @@ void EmulatedController::LoadDevices() {
     battery_params[LeftIndex].Set("battery", true);
     battery_params[RightIndex].Set("battery", true);
 
-    camera_params = Common::ParamPackage{"engine:camera,camera:1"};
+    camera_params[0] = right_joycon;
+    camera_params[0].Set("camera", true);
+    camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
     ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
     nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
     nfc_params[1] = right_joycon;
@@ -153,7 +155,7 @@ void EmulatedController::LoadDevices() {
 
     output_params[LeftIndex] = left_joycon;
     output_params[RightIndex] = right_joycon;
-    output_params[2] = camera_params;
+    output_params[2] = camera_params[1];
     output_params[3] = nfc_params[0];
     output_params[LeftIndex].Set("output", true);
     output_params[RightIndex].Set("output", true);
@@ -171,7 +173,7 @@ void EmulatedController::LoadDevices() {
     std::ranges::transform(battery_params, battery_devices.begin(),
                            Common::Input::CreateInputDevice);
     std::ranges::transform(color_params, color_devices.begin(), Common::Input::CreateInputDevice);
-    camera_devices = Common::Input::CreateInputDevice(camera_params);
+    std::ranges::transform(camera_params, camera_devices.begin(), Common::Input::CreateInputDevice);
     std::ranges::transform(ring_params, ring_analog_devices.begin(),
                            Common::Input::CreateInputDevice);
     std::ranges::transform(nfc_params, nfc_devices.begin(), Common::Input::CreateInputDevice);
@@ -362,12 +364,15 @@ void EmulatedController::ReloadInput() {
         motion_devices[index]->ForceUpdate();
     }
 
-    if (camera_devices) {
-        camera_devices->SetCallback({
+    for (std::size_t index = 0; index < camera_devices.size(); ++index) {
+        if (!camera_devices[index]) {
+            continue;
+        }
+        camera_devices[index]->SetCallback({
             .on_change =
                 [this](const Common::Input::CallbackStatus& callback) { SetCamera(callback); },
         });
-        camera_devices->ForceUpdate();
+        camera_devices[index]->ForceUpdate();
     }
 
     for (std::size_t index = 0; index < ring_analog_devices.size(); ++index) {
@@ -477,7 +482,9 @@ void EmulatedController::UnloadInput() {
     for (auto& stick : virtual_stick_devices) {
         stick.reset();
     }
-    camera_devices.reset();
+    for (auto& camera : camera_devices) {
+        camera.reset();
+    }
     for (auto& ring : ring_analog_devices) {
         ring.reset();
     }
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index fb931fc0a8..edebfc15c2 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -39,7 +39,8 @@ using ColorDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using BatteryDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
-using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
+using CameraDevices =
+    std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using RingAnalogDevices =
     std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
 using NfcDevices =
@@ -52,7 +53,7 @@ using ControllerMotionParams = std::array<Common::ParamPackage, Settings::Native
 using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
 using ColorParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
-using CameraParams = Common::ParamPackage;
+using CameraParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using RingAnalogParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using NfcParams = std::array<Common::ParamPackage, max_emulated_controllers>;
 using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 6a3453457f..3c1fa2274f 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -74,6 +74,8 @@ void IRS::DeactivateIrsensor(Kernel::HLERequestContext& ctx) {
     LOG_WARNING(Service_IRS, "(STUBBED) called, applet_resource_user_id={}",
                 applet_resource_user_id);
 
+    npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+
     IPC::ResponseBuilder rb{ctx, 2};
     rb.Push(ResultSuccess);
 }
@@ -108,6 +110,7 @@ void IRS::StopImageProcessor(Kernel::HLERequestContext& ctx) {
     auto result = IsIrCameraHandleValid(parameters.camera_handle);
     if (result.IsSuccess()) {
         // TODO: Stop Image processor
+        npad_device->SetPollingMode(Common::Input::PollingMode::Active);
         result = ResultSuccess;
     }
 
@@ -139,6 +142,7 @@ void IRS::RunMomentProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<MomentProcessor>(parameters.camera_handle, device);
         auto& image_transfer_processor = GetProcessor<MomentProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -170,6 +174,7 @@ void IRS::RunClusteringProcessor(Kernel::HLERequestContext& ctx) {
         auto& image_transfer_processor =
             GetProcessor<ClusteringProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -219,6 +224,7 @@ void IRS::RunImageTransferProcessor(Kernel::HLERequestContext& ctx) {
             GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
         image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -294,6 +300,7 @@ void IRS::RunTeraPluginProcessor(Kernel::HLERequestContext& ctx) {
         auto& image_transfer_processor =
             GetProcessor<TeraPluginProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -343,6 +350,7 @@ void IRS::RunPointingProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<PointingProcessor>(camera_handle, device);
         auto& image_transfer_processor = GetProcessor<PointingProcessor>(camera_handle);
         image_transfer_processor.SetConfig(processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -453,6 +461,7 @@ void IRS::RunImageTransferExProcessor(Kernel::HLERequestContext& ctx) {
             GetProcessor<ImageTransferProcessor>(parameters.camera_handle);
         image_transfer_processor.SetConfig(parameters.processor_config);
         image_transfer_processor.SetTransferMemoryPointer(transfer_memory);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -479,6 +488,7 @@ void IRS::RunIrLedProcessor(Kernel::HLERequestContext& ctx) {
         MakeProcessor<IrLedProcessor>(camera_handle, device);
         auto& image_transfer_processor = GetProcessor<IrLedProcessor>(camera_handle);
         image_transfer_processor.SetConfig(processor_config);
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
     }
 
     IPC::ResponseBuilder rb{ctx, 2};
@@ -504,6 +514,7 @@ void IRS::StopImageProcessorAsync(Kernel::HLERequestContext& ctx) {
     auto result = IsIrCameraHandleValid(parameters.camera_handle);
     if (result.IsSuccess()) {
         // TODO: Stop image processor async
+        npad_device->SetPollingMode(Common::Input::PollingMode::IR);
         result = ResultSuccess;
     }
 
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 9c901af2ab..e3b627e4ff 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -64,6 +64,8 @@ if (ENABLE_SDL2)
         helpers/joycon_protocol/generic_functions.cpp
         helpers/joycon_protocol/generic_functions.h
         helpers/joycon_protocol/joycon_types.h
+        helpers/joycon_protocol/irs.cpp
+        helpers/joycon_protocol/irs.h
         helpers/joycon_protocol/nfc.cpp
         helpers/joycon_protocol/nfc.h
         helpers/joycon_protocol/poller.cpp
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index cf54f1b533..6c03e09537 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -191,6 +191,10 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
             .on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
                 OnAmiiboUpdate(port, amiibo_data);
             }},
+            .on_camera_data = {[this, port](const std::vector<u8>& camera_data,
+                                            Joycon::IrsResolution format) {
+                OnCameraUpdate(port, camera_data, format);
+            }},
         };
 
         handle->InitializeDevice();
@@ -265,9 +269,14 @@ Common::Input::DriverResult Joycons::SetLeds(const PadIdentifier& identifier,
         handle->SetLedConfig(static_cast<u8>(led_config)));
 }
 
-Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier_,
+Common::Input::DriverResult Joycons::SetCameraFormat(const PadIdentifier& identifier,
                                                      Common::Input::CameraFormat camera_format) {
-    return Common::Input::DriverResult::NotSupported;
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return Common::Input::DriverResult::InvalidHandle;
+    }
+    return static_cast<Common::Input::DriverResult>(handle->SetIrsConfig(
+        Joycon::IrsMode::ImageTransfer, static_cast<Joycon::IrsResolution>(camera_format)));
 };
 
 Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
@@ -288,18 +297,16 @@ Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identif
     }
 
     switch (polling_mode) {
-    case Common::Input::PollingMode::NFC:
-        return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
-        break;
     case Common::Input::PollingMode::Active:
         return static_cast<Common::Input::DriverResult>(handle->SetActiveMode());
-        break;
     case Common::Input::PollingMode::Pasive:
         return static_cast<Common::Input::DriverResult>(handle->SetPasiveMode());
-        break;
+    case Common::Input::PollingMode::IR:
+        return static_cast<Common::Input::DriverResult>(handle->SetIrMode());
+    case Common::Input::PollingMode::NFC:
+        return static_cast<Common::Input::DriverResult>(handle->SetNfcMode());
     case Common::Input::PollingMode::Ring:
         return static_cast<Common::Input::DriverResult>(handle->SetRingConMode());
-        break;
     default:
         return Common::Input::DriverResult::NotSupported;
     }
@@ -390,6 +397,12 @@ void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_dat
     SetNfc(identifier, {nfc_state, amiibo_data});
 }
 
+void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
+                             Joycon::IrsResolution format) {
+    const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
+    SetCamera(identifier, {static_cast<Common::Input::CameraFormat>(format), camera_data});
+}
+
 std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
     auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
         if (!device) {
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
index 1a04c19fd3..f180b74783 100644
--- a/src/input_common/drivers/joycon.h
+++ b/src/input_common/drivers/joycon.h
@@ -17,6 +17,7 @@ struct Color;
 struct MotionData;
 enum class ControllerType;
 enum class DriverResult;
+enum class IrsResolution;
 class JoyconDriver;
 } // namespace InputCommon::Joycon
 
@@ -35,7 +36,7 @@ public:
     Common::Input::DriverResult SetLeds(const PadIdentifier& identifier,
                                         const Common::Input::LedStatus& led_status) override;
 
-    Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier_,
+    Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
                                                 Common::Input::CameraFormat camera_format) override;
 
     Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
@@ -81,6 +82,8 @@ private:
                         const Joycon::MotionData& value);
     void OnRingConUpdate(f32 ring_data);
     void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
+    void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
+                        Joycon::IrsResolution format);
 
     /// Returns a JoyconHandle corresponding to a PadIdentifier
     std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 8217ba7f61..040832a4ba 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -7,6 +7,7 @@
 #include "input_common/helpers/joycon_driver.h"
 #include "input_common/helpers/joycon_protocol/calibration.h"
 #include "input_common/helpers/joycon_protocol/generic_functions.h"
+#include "input_common/helpers/joycon_protocol/irs.h"
 #include "input_common/helpers/joycon_protocol/nfc.h"
 #include "input_common/helpers/joycon_protocol/poller.h"
 #include "input_common/helpers/joycon_protocol/ringcon.h"
@@ -78,6 +79,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     // Initialize HW Protocols
     calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
     generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
+    irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
     nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
     ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
     rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
@@ -200,10 +202,15 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
         .min_value = ring_calibration.min_value,
     };
 
+    if (irs_protocol->IsEnabled()) {
+        irs_protocol->RequestImage(buffer);
+        joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
+    }
+
     if (nfc_protocol->IsEnabled()) {
         if (amiibo_detected) {
             if (!nfc_protocol->HasAmiibo()) {
-                joycon_poller->updateAmiibo({});
+                joycon_poller->UpdateAmiibo({});
                 amiibo_detected = false;
                 return;
             }
@@ -213,7 +220,7 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
             std::vector<u8> data(0x21C);
             const auto result = nfc_protocol->ScanAmiibo(data);
             if (result == DriverResult::Success) {
-                joycon_poller->updateAmiibo(data);
+                joycon_poller->UpdateAmiibo(data);
                 amiibo_detected = true;
             }
         }
@@ -251,6 +258,20 @@ DriverResult JoyconDriver::SetPollingMode() {
         generic_protocol->EnableImu(false);
     }
 
+    if (irs_protocol->IsEnabled()) {
+        irs_protocol->DisableIrs();
+    }
+
+    if (irs_enabled && supported_features.irs) {
+        auto result = irs_protocol->EnableIrs();
+        if (result == DriverResult::Success) {
+            disable_input_thread = false;
+            return result;
+        }
+        irs_protocol->DisableIrs();
+        LOG_ERROR(Input, "Error enabling IRS");
+    }
+
     if (nfc_protocol->IsEnabled()) {
         amiibo_detected = false;
         nfc_protocol->DisableNfc();
@@ -375,12 +396,24 @@ DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
     return generic_protocol->SetLedPattern(led_pattern);
 }
 
+DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
+    std::scoped_lock lock{mutex};
+    if (disable_input_thread) {
+        return DriverResult::HandleInUse;
+    }
+    disable_input_thread = true;
+    const auto result = irs_protocol->SetIrsConfig(mode_, format_);
+    disable_input_thread = false;
+    return result;
+}
+
 DriverResult JoyconDriver::SetPasiveMode() {
     std::scoped_lock lock{mutex};
     motion_enabled = false;
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = true;
+    irs_enabled = false;
     return SetPollingMode();
 }
 
@@ -390,6 +423,22 @@ DriverResult JoyconDriver::SetActiveMode() {
     hidbus_enabled = false;
     nfc_enabled = false;
     passive_enabled = false;
+    irs_enabled = false;
+    return SetPollingMode();
+}
+
+DriverResult JoyconDriver::SetIrMode() {
+    std::scoped_lock lock{mutex};
+
+    if (!supported_features.irs) {
+        return DriverResult::NotSupported;
+    }
+
+    motion_enabled = false;
+    hidbus_enabled = false;
+    nfc_enabled = false;
+    passive_enabled = false;
+    irs_enabled = true;
     return SetPollingMode();
 }
 
@@ -404,6 +453,7 @@ DriverResult JoyconDriver::SetNfcMode() {
     hidbus_enabled = false;
     nfc_enabled = true;
     passive_enabled = false;
+    irs_enabled = false;
     return SetPollingMode();
 }
 
@@ -418,6 +468,7 @@ DriverResult JoyconDriver::SetRingConMode() {
     hidbus_enabled = true;
     nfc_enabled = false;
     passive_enabled = false;
+    irs_enabled = false;
 
     const auto result = SetPollingMode();
 
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index 5ff15c7841..61ecf4a6c2 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -13,6 +13,7 @@
 namespace InputCommon::Joycon {
 class CalibrationProtocol;
 class GenericProtocol;
+class IrsProtocol;
 class NfcProtocol;
 class JoyconPoller;
 class RingConProtocol;
@@ -41,8 +42,10 @@ public:
 
     DriverResult SetVibration(const VibrationValue& vibration);
     DriverResult SetLedConfig(u8 led_pattern);
+    DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
     DriverResult SetPasiveMode();
     DriverResult SetActiveMode();
+    DriverResult SetIrMode();
     DriverResult SetNfcMode();
     DriverResult SetRingConMode();
 
@@ -87,6 +90,7 @@ private:
     // Protocol Features
     std::unique_ptr<CalibrationProtocol> calibration_protocol;
     std::unique_ptr<GenericProtocol> generic_protocol;
+    std::unique_ptr<IrsProtocol> irs_protocol;
     std::unique_ptr<NfcProtocol> nfc_protocol;
     std::unique_ptr<JoyconPoller> joycon_poller;
     std::unique_ptr<RingConProtocol> ring_protocol;
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index a4d08fdafa..a329db1070 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -120,6 +120,19 @@ DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const
     return DriverResult::Success;
 }
 
+DriverResult JoyconCommonProtocol::SendMcuCommand(SubCommand sc, std::span<const u8> buffer) {
+    std::vector<u8> local_buffer(MaxResponseSize);
+
+    local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
+    local_buffer[1] = GetCounter();
+    local_buffer[10] = static_cast<u8>(sc);
+    for (std::size_t i = 0; i < buffer.size(); ++i) {
+        local_buffer[11 + i] = buffer[i];
+    }
+
+    return SendData(local_buffer);
+}
+
 DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
     std::vector<u8> local_buffer(MaxResponseSize);
 
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
index a65e4aa76a..2a3feaf598 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.h
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -74,6 +74,13 @@ public:
      */
     DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
 
+    /**
+     * Sends a mcu command to the device
+     * @param sc sub command to be send
+     * @param buffer data to be send
+     */
+    DriverResult SendMcuCommand(SubCommand sc, std::span<const u8> buffer);
+
     /**
      * Sends vibration data to the joycon
      * @param buffer data to be send
diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp
new file mode 100644
index 0000000000..9dfa503c23
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.cpp
@@ -0,0 +1,300 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <thread>
+#include "common/logging/log.h"
+#include "input_common/helpers/joycon_protocol/irs.h"
+
+namespace InputCommon::Joycon {
+
+IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
+    : JoyconCommonProtocol(std::move(handle)) {}
+
+DriverResult IrsProtocol::EnableIrs() {
+    LOG_INFO(Input, "Enable IRS");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
+    }
+    if (result == DriverResult::Success) {
+        result = EnableMCU(true);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
+    }
+    if (result == DriverResult::Success) {
+        const MCUConfig config{
+            .command = MCUCommand::ConfigureMCU,
+            .sub_command = MCUSubCommand::SetMCUMode,
+            .mode = MCUMode::IR,
+            .crc = {},
+        };
+
+        result = ConfigureMCU(config);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
+    }
+    if (result == DriverResult::Success) {
+        result = ConfigureIrs();
+    }
+    if (result == DriverResult::Success) {
+        result = WriteRegistersStep1();
+    }
+    if (result == DriverResult::Success) {
+        result = WriteRegistersStep2();
+    }
+
+    is_enabled = true;
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult IrsProtocol::DisableIrs() {
+    LOG_DEBUG(Input, "Disable IRS");
+    DriverResult result{DriverResult::Success};
+    SetBlocking();
+
+    if (result == DriverResult::Success) {
+        result = EnableMCU(false);
+    }
+
+    is_enabled = false;
+
+    SetNonBlocking();
+    return result;
+}
+
+DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) {
+    irs_mode = mode;
+    switch (format) {
+    case IrsResolution::Size320x240:
+        resolution_code = IrsResolutionCode::Size320x240;
+        fragments = IrsFragments::Size320x240;
+        resolution = IrsResolution::Size320x240;
+        break;
+    case IrsResolution::Size160x120:
+        resolution_code = IrsResolutionCode::Size160x120;
+        fragments = IrsFragments::Size160x120;
+        resolution = IrsResolution::Size160x120;
+        break;
+    case IrsResolution::Size80x60:
+        resolution_code = IrsResolutionCode::Size80x60;
+        fragments = IrsFragments::Size80x60;
+        resolution = IrsResolution::Size80x60;
+        break;
+    case IrsResolution::Size20x15:
+        resolution_code = IrsResolutionCode::Size20x15;
+        fragments = IrsFragments::Size20x15;
+        resolution = IrsResolution::Size20x15;
+        break;
+    case IrsResolution::Size40x30:
+    default:
+        resolution_code = IrsResolutionCode::Size40x30;
+        fragments = IrsFragments::Size40x30;
+        resolution = IrsResolution::Size40x30;
+        break;
+    }
+
+    // Restart feature
+    if (is_enabled) {
+        DisableIrs();
+        return EnableIrs();
+    }
+
+    return DriverResult::Success;
+}
+
+DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) {
+    const u8 next_packet_fragment =
+        static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1));
+
+    if (buffer[0] == 0x31 && buffer[49] == 0x03) {
+        u8 new_packet_fragment = buffer[52];
+        if (new_packet_fragment == next_packet_fragment) {
+            packet_fragment = next_packet_fragment;
+            memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300);
+
+            return RequestFrame(packet_fragment);
+        }
+
+        if (new_packet_fragment == packet_fragment) {
+            return RequestFrame(packet_fragment);
+        }
+
+        return ResendFrame(next_packet_fragment);
+    }
+
+    return RequestFrame(packet_fragment);
+}
+
+DriverResult IrsProtocol::ConfigureIrs() {
+    LOG_DEBUG(Input, "Configure IRS");
+    constexpr std::size_t max_tries = 28;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    const IrsConfigure irs_configuration{
+        .command = MCUCommand::ConfigureIR,
+        .sub_command = MCUSubCommand::SetDeviceMode,
+        .irs_mode = IrsMode::ImageTransfer,
+        .number_of_fragments = fragments,
+        .mcu_major_version = 0x0500,
+        .mcu_minor_version = 0x1800,
+        .crc = {},
+    };
+    buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
+
+    std::vector<u8> request_data(sizeof(IrsConfigure));
+    memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    do {
+        const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ >= max_tries) {
+            return DriverResult::WrongReply;
+        }
+    } while (output[15] != 0x0b);
+
+    return DriverResult::Success;
+}
+
+DriverResult IrsProtocol::WriteRegistersStep1() {
+    LOG_DEBUG(Input, "WriteRegistersStep1");
+    DriverResult result{DriverResult::Success};
+    constexpr std::size_t max_tries = 28;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    const IrsWriteRegisters irs_registers{
+        .command = MCUCommand::ConfigureIR,
+        .sub_command = MCUSubCommand::WriteDeviceRegisters,
+        .number_of_registers = 0x9,
+        .registers =
+            {
+                IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)},
+                {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)},
+                {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)},
+                {IrRegistersAddress::ExposureTime, 0x00},
+                {IrRegistersAddress::Leds, static_cast<u8>(leds)},
+                {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)},
+                {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)},
+                {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)},
+                {IrRegistersAddress::WhitePixelThreshold, 0xc8},
+            },
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
+    memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+
+    std::array<u8, 38> mcu_request{0x02};
+    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
+    mcu_request[37] = 0xFF;
+
+    if (result != DriverResult::Success) {
+        return result;
+    }
+
+    do {
+        result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
+
+        // First time we need to set the report mode
+        if (result == DriverResult::Success && tries == 0) {
+            result = SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+        }
+        if (result == DriverResult::Success && tries == 0) {
+            GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
+        }
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ >= max_tries) {
+            return DriverResult::WrongReply;
+        }
+    } while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23);
+
+    return DriverResult::Success;
+}
+
+DriverResult IrsProtocol::WriteRegistersStep2() {
+    LOG_DEBUG(Input, "WriteRegistersStep2");
+    constexpr std::size_t max_tries = 28;
+    std::vector<u8> output;
+    std::size_t tries = 0;
+
+    const IrsWriteRegisters irs_registers{
+        .command = MCUCommand::ConfigureIR,
+        .sub_command = MCUSubCommand::WriteDeviceRegisters,
+        .number_of_registers = 0x8,
+        .registers =
+            {
+                IrsRegister{IrRegistersAddress::LedIntensitiyMSB,
+                            static_cast<u8>(led_intensity >> 8)},
+                {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)},
+                {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)},
+                {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)},
+                {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)},
+                {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)},
+                {IrRegistersAddress::UpdateTime, 0x2d},
+                {IrRegistersAddress::FinalizeConfig, 0x01},
+            },
+        .crc = {},
+    };
+
+    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
+    memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
+    request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
+    do {
+        const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+        if (tries++ >= max_tries) {
+            return DriverResult::WrongReply;
+        }
+    } while (output[15] != 0x13 && output[15] != 0x23);
+
+    return DriverResult::Success;
+}
+
+DriverResult IrsProtocol::RequestFrame(u8 frame) {
+    std::array<u8, 38> mcu_request{};
+    mcu_request[3] = frame;
+    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
+    mcu_request[37] = 0xFF;
+    return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+}
+
+DriverResult IrsProtocol::ResendFrame(u8 frame) {
+    std::array<u8, 38> mcu_request{};
+    mcu_request[1] = 0x1;
+    mcu_request[2] = frame;
+    mcu_request[3] = 0x0;
+    mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
+    mcu_request[37] = 0xFF;
+    return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+}
+
+std::vector<u8> IrsProtocol::GetImage() const {
+    return buf_image;
+}
+
+IrsResolution IrsProtocol::GetIrsFormat() const {
+    return resolution;
+}
+
+bool IrsProtocol::IsEnabled() const {
+    return is_enabled;
+}
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h
new file mode 100644
index 0000000000..76dfa02ea5
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.h
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
+// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
+// https://github.com/CTCaer/jc_toolkit
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
+
+#pragma once
+
+#include <vector>
+
+#include "input_common/helpers/joycon_protocol/common_protocol.h"
+#include "input_common/helpers/joycon_protocol/joycon_types.h"
+
+namespace InputCommon::Joycon {
+
+class IrsProtocol final : private JoyconCommonProtocol {
+public:
+    explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle);
+
+    DriverResult EnableIrs();
+
+    DriverResult DisableIrs();
+
+    DriverResult SetIrsConfig(IrsMode mode, IrsResolution format);
+
+    DriverResult RequestImage(std::span<u8> buffer);
+
+    std::vector<u8> GetImage() const;
+
+    IrsResolution GetIrsFormat() const;
+
+    bool IsEnabled() const;
+
+private:
+    DriverResult ConfigureIrs();
+
+    DriverResult WriteRegistersStep1();
+    DriverResult WriteRegistersStep2();
+
+    DriverResult RequestFrame(u8 frame);
+    DriverResult ResendFrame(u8 frame);
+
+    IrsMode irs_mode{IrsMode::ImageTransfer};
+    IrsResolution resolution{IrsResolution::Size40x30};
+    IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30};
+    IrsFragments fragments{IrsFragments::Size40x30};
+    IrLeds leds{IrLeds::BrightAndDim};
+    IrExLedFilter led_filter{IrExLedFilter::Enabled};
+    IrImageFlip image_flip{IrImageFlip::Normal};
+    u8 digital_gain{0x01};
+    u16 exposure{0x2490};
+    u16 led_intensity{0x0f10};
+    u32 denoise{0x012344};
+
+    u8 packet_fragment{};
+    std::vector<u8> buf_image; // 8bpp greyscale image.
+
+    bool is_enabled{};
+};
+
+} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
index 36c00a8d70..273c8d07d3 100644
--- a/src/input_common/helpers/joycon_protocol/joycon_types.h
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -18,7 +18,7 @@
 
 namespace InputCommon::Joycon {
 constexpr u32 MaxErrorCount = 50;
-constexpr u32 MaxBufferSize = 60;
+constexpr u32 MaxBufferSize = 368;
 constexpr u32 MaxResponseSize = 49;
 constexpr u32 MaxSubCommandResponseSize = 64;
 constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
@@ -273,6 +273,80 @@ enum class NFCTagType : u8 {
     Ntag215 = 0x01,
 };
 
+enum class IrsMode : u8 {
+    None = 0x02,
+    Moment = 0x03,
+    Dpd = 0x04,
+    Clustering = 0x06,
+    ImageTransfer = 0x07,
+    Silhouette = 0x08,
+    TeraImage = 0x09,
+    SilhouetteTeraImage = 0x0A,
+};
+
+enum class IrsResolution {
+    Size320x240,
+    Size160x120,
+    Size80x60,
+    Size40x30,
+    Size20x15,
+    None,
+};
+
+enum class IrsResolutionCode : u8 {
+    Size320x240 = 0x00, // Full pixel array
+    Size160x120 = 0x50, // Sensor Binning [2 X 2]
+    Size80x60 = 0x64,   // Sensor Binning [4 x 2] and Skipping [1 x 2]
+    Size40x30 = 0x69,   // Sensor Binning [4 x 2] and Skipping [2 x 4]
+    Size20x15 = 0x6A,   // Sensor Binning [4 x 2] and Skipping [4 x 4]
+};
+
+// Size of image divided by 300
+enum class IrsFragments : u8 {
+    Size20x15 = 0x00,
+    Size40x30 = 0x03,
+    Size80x60 = 0x0f,
+    Size160x120 = 0x3f,
+    Size320x240 = 0xFF,
+};
+
+enum class IrLeds : u8 {
+    BrightAndDim = 0x00,
+    Bright = 0x20,
+    Dim = 0x10,
+    None = 0x30,
+};
+
+enum class IrExLedFilter : u8 {
+    Disabled = 0x00,
+    Enabled = 0x03,
+};
+
+enum class IrImageFlip : u8 {
+    Normal = 0x00,
+    Inverted = 0x02,
+};
+
+enum class IrRegistersAddress : u16 {
+    UpdateTime = 0x0400,
+    FinalizeConfig = 0x0700,
+    LedFilter = 0x0e00,
+    Leds = 0x1000,
+    LedIntensitiyMSB = 0x1100,
+    LedIntensitiyLSB = 0x1200,
+    ImageFlip = 0x2d00,
+    Resolution = 0x2e00,
+    DigitalGainLSB = 0x2e01,
+    DigitalGainMSB = 0x2f01,
+    ExposureLSB = 0x3001,
+    ExposureMSB = 0x3101,
+    ExposureTime = 0x3201,
+    WhitePixelThreshold = 0x4301,
+    DenoiseSmoothing = 0x6701,
+    DenoiseEdge = 0x6801,
+    DenoiseColor = 0x6901,
+};
+
 enum class DriverResult {
     Success,
     WrongReply,
@@ -456,6 +530,36 @@ struct NFCRequestState {
 };
 static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
 
+struct IrsConfigure {
+    MCUCommand command;
+    MCUSubCommand sub_command;
+    IrsMode irs_mode;
+    IrsFragments number_of_fragments;
+    u16 mcu_major_version;
+    u16 mcu_minor_version;
+    INSERT_PADDING_BYTES(0x1D);
+    u8 crc;
+};
+static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
+
+#pragma pack(push, 1)
+struct IrsRegister {
+    IrRegistersAddress address;
+    u8 value;
+};
+static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
+
+struct IrsWriteRegisters {
+    MCUCommand command;
+    MCUSubCommand sub_command;
+    u8 number_of_registers;
+    std::array<IrsRegister, 9> registers;
+    INSERT_PADDING_BYTES(0x7);
+    u8 crc;
+};
+static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
+#pragma pack(pop)
+
 struct FirmwareVersion {
     u8 major;
     u8 minor;
@@ -490,6 +594,7 @@ struct JoyconCallbacks {
     std::function<void(int, const MotionData&)> on_motion_data;
     std::function<void(f32)> on_ring_data;
     std::function<void(const std::vector<u8>&)> on_amiibo_data;
+    std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
 };
 
 } // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index fd05d98f38..940b20b7f0 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -74,10 +74,14 @@ void JoyconPoller::UpdateColor(const Color& color) {
     callbacks.on_color_data(color);
 }
 
-void JoyconPoller::updateAmiibo(const std::vector<u8>& amiibo_data) {
+void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) {
     callbacks.on_amiibo_data(amiibo_data);
 }
 
+void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
+    callbacks.on_camera_data(camera_data, format);
+}
+
 void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
     float normalized_value = static_cast<float>(value - ring_status.default_value);
     if (normalized_value > 0) {
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
index c40fc7bca8..354d41dad3 100644
--- a/src/input_common/helpers/joycon_protocol/poller.h
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -36,7 +36,8 @@ public:
 
     void UpdateColor(const Color& color);
     void UpdateRing(s16 value, const RingStatus& ring_status);
-    void updateAmiibo(const std::vector<u8>& amiibo_data);
+    void UpdateAmiibo(const std::vector<u8>& amiibo_data);
+    void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
 
 private:
     void UpdateActiveLeftPadInput(const InputReportActive& input,
-- 
cgit v1.2.3-70-g09d2


From 340f15d1fa79594dbe12a6e19140ba012751b533 Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Fri, 13 Jan 2023 23:29:05 -0600
Subject: input_common: Address byte review

---
 src/input_common/drivers/joycon.cpp                |  50 +++---
 src/input_common/drivers/joycon.h                  |   1 -
 src/input_common/drivers/sdl_driver.cpp            |   2 +-
 src/input_common/helpers/joycon_driver.cpp         |   8 +-
 src/input_common/helpers/joycon_driver.h           |   3 +-
 .../helpers/joycon_protocol/calibration.cpp        |   9 +-
 .../helpers/joycon_protocol/common_protocol.cpp    |  24 +--
 .../helpers/joycon_protocol/common_protocol.h      |  22 ++-
 .../helpers/joycon_protocol/generic_functions.cpp  |  54 ++----
 src/input_common/helpers/joycon_protocol/irs.cpp   |  18 +-
 .../helpers/joycon_protocol/joycon_types.h         |  12 ++
 src/input_common/helpers/joycon_protocol/nfc.cpp   | 183 ++++++++++-----------
 src/input_common/helpers/joycon_protocol/nfc.h     |   4 +-
 .../helpers/joycon_protocol/poller.cpp             |  18 +-
 .../helpers/joycon_protocol/ringcon.cpp            |  36 ++--
 .../helpers/joycon_protocol/rumble.cpp             |  19 +--
 16 files changed, 220 insertions(+), 243 deletions(-)

(limited to 'src/input_common/helpers/joycon_protocol/poller.cpp')

diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index fff886ca8a..1582def13c 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -60,15 +60,12 @@ void Joycons::Setup() {
         device = std::make_shared<Joycon::JoyconDriver>(port++);
     }
 
-    if (!scan_thread_running) {
-        scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
-    }
+    scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
 }
 
 void Joycons::ScanThread(std::stop_token stop_token) {
     constexpr u16 nintendo_vendor_id = 0x057e;
-    Common::SetCurrentThreadName("yuzu:input:JoyconScanThread");
-    scan_thread_running = true;
+    Common::SetCurrentThreadName("JoyconScanThread");
     while (!stop_token.stop_requested()) {
         SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
         SDL_hid_device_info* cur_dev = devs;
@@ -82,9 +79,9 @@ void Joycons::ScanThread(std::stop_token stop_token) {
             cur_dev = cur_dev->next;
         }
 
+        SDL_hid_free_enumeration(devs);
         std::this_thread::sleep_for(std::chrono::seconds(5));
     }
-    scan_thread_running = false;
 }
 
 bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
@@ -185,19 +182,19 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
 
 std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
     Joycon::ControllerType type) const {
-
     if (type == Joycon::ControllerType::Left) {
-        for (const auto& device : left_joycons) {
-            if (!device->IsConnected()) {
-                return device;
-            }
+        const auto unconnected_device =
+            std::ranges::find_if(left_joycons, [](auto& device) { return !device->IsConnected(); });
+        if (unconnected_device != left_joycons.end()) {
+            return *unconnected_device;
         }
     }
     if (type == Joycon::ControllerType::Right) {
-        for (const auto& device : right_joycons) {
-            if (!device->IsConnected()) {
-                return device;
-            }
+        const auto unconnected_device = std::ranges::find_if(
+            right_joycons, [](auto& device) { return !device->IsConnected(); });
+
+        if (unconnected_device != right_joycons.end()) {
+            return *unconnected_device;
         }
     }
     return nullptr;
@@ -391,20 +388,25 @@ std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifie
         return false;
     };
     const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
+
     if (type == Joycon::ControllerType::Left) {
-        for (const auto& device : left_joycons) {
-            if (is_handle_active(device)) {
-                return device;
-            }
+        const auto matching_device = std::ranges::find_if(
+            left_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
+
+        if (matching_device != left_joycons.end()) {
+            return *matching_device;
         }
     }
+
     if (type == Joycon::ControllerType::Right) {
-        for (const auto& device : right_joycons) {
-            if (is_handle_active(device)) {
-                return device;
-            }
+        const auto matching_device = std::ranges::find_if(
+            right_joycons, [is_handle_active](auto& device) { return is_handle_active(device); });
+
+        if (matching_device != right_joycons.end()) {
+            return *matching_device;
         }
     }
+
     return nullptr;
 }
 
@@ -676,7 +678,7 @@ std::string Joycons::JoyconName(Joycon::ControllerType type) const {
     case Joycon::ControllerType::Dual:
         return "Dual Joycon";
     default:
-        return "Unknow Joycon";
+        return "Unknown Joycon";
     }
 }
 } // namespace InputCommon
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
index f5cc787db0..6d2e2ec786 100644
--- a/src/input_common/drivers/joycon.h
+++ b/src/input_common/drivers/joycon.h
@@ -99,7 +99,6 @@ private:
     std::string JoyconName(Joycon::ControllerType type) const;
 
     std::jthread scan_thread;
-    bool scan_thread_running{};
 
     // Joycon types are split by type to ease supporting dualjoycon configurations
     std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index e915ec0908..a0103edde2 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -321,7 +321,7 @@ void SDLDriver::InitJoystick(int joystick_index) {
     if (Settings::values.enable_joycon_driver) {
         if (guid.uuid[5] == 0x05 && guid.uuid[4] == 0x7e &&
             (guid.uuid[8] == 0x06 || guid.uuid[8] == 0x07)) {
-            LOG_ERROR(Input, "Device black listed {}", joystick_index);
+            LOG_WARNING(Input, "Preferring joycon driver for device index {}", joystick_index);
             SDL_JoystickClose(sdl_joystick);
             return;
         }
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 5525723435..4159e5717a 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -123,7 +123,7 @@ DriverResult JoyconDriver::InitializeDevice() {
 }
 
 void JoyconDriver::InputThread(std::stop_token stop_token) {
-    LOG_INFO(Input, "JC Adapter input thread started");
+    LOG_INFO(Input, "Joycon Adapter input thread started");
     Common::SetCurrentThreadName("JoyconInput");
     input_thread_running = true;
 
@@ -157,7 +157,7 @@ void JoyconDriver::InputThread(std::stop_token stop_token) {
 
     is_connected = false;
     input_thread_running = false;
-    LOG_INFO(Input, "JC Adapter input thread stopped");
+    LOG_INFO(Input, "Joycon Adapter input thread stopped");
 }
 
 void JoyconDriver::OnNewData(std::span<u8> buffer) {
@@ -349,7 +349,7 @@ JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
 }
 
 bool JoyconDriver::IsInputThreadValid() const {
-    if (!is_connected) {
+    if (!is_connected.load()) {
         return false;
     }
     if (hidapi_handle->handle == nullptr) {
@@ -491,7 +491,7 @@ DriverResult JoyconDriver::SetRingConMode() {
 
 bool JoyconDriver::IsConnected() const {
     std::scoped_lock lock{mutex};
-    return is_connected;
+    return is_connected.load();
 }
 
 bool JoyconDriver::IsVibrationEnabled() const {
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index e8e65e1331..c1e189fa52 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -3,6 +3,7 @@
 
 #pragma once
 
+#include <atomic>
 #include <functional>
 #include <mutex>
 #include <span>
@@ -97,7 +98,7 @@ private:
     std::unique_ptr<RumbleProtocol> rumble_protocol;
 
     // Connection status
-    bool is_connected{};
+    std::atomic<bool> is_connected{};
     u64 delta_time;
     std::size_t error_counter{};
     std::shared_ptr<JoyconHandle> hidapi_handle;
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
index cd30ab8698..f6e7e97d59 100644
--- a/src/input_common/helpers/joycon_protocol/calibration.cpp
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -12,10 +12,10 @@ CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
     : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
     DriverResult result{DriverResult::Success};
     calibration = {};
-    SetBlocking();
 
     result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
 
@@ -44,15 +44,14 @@ DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration
     // Set a valid default calibration if data is missing
     ValidateCalibration(calibration);
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
     DriverResult result{DriverResult::Success};
     calibration = {};
-    SetBlocking();
 
     result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
 
@@ -81,15 +80,14 @@ DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibratio
     // Set a valid default calibration if data is missing
     ValidateCalibration(calibration);
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
     DriverResult result{DriverResult::Success};
     calibration = {};
-    SetBlocking();
 
     result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
 
@@ -124,7 +122,6 @@ DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibrati
 
     ValidateCalibration(calibration);
 
-    SetNonBlocking();
     return result;
 }
 
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index 153a3908c6..417d0dcc50 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -58,9 +58,8 @@ DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device
 }
 
 DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
-    const std::vector<u8> buffer{static_cast<u8>(report_mode)};
-    std::vector<u8> output;
-    return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer, output);
+    const std::array<u8, 1> buffer{static_cast<u8>(report_mode)};
+    return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer);
 }
 
 DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
@@ -120,7 +119,12 @@ DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const
     return DriverResult::Success;
 }
 
-DriverResult JoyconCommonProtocol::SendMcuCommand(SubCommand sc, std::span<const u8> buffer) {
+DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
+    std::vector<u8> output;
+    return SendSubCommand(sc, buffer, output);
+}
+
+DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
     std::vector<u8> local_buffer(MaxResponseSize);
 
     local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
@@ -147,7 +151,7 @@ DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffe
 DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
     constexpr std::size_t MaxTries = 10;
     std::size_t tries = 0;
-    std::vector<u8> buffer = {0x00, 0x00, 0x00, 0x00, size};
+    std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size};
     std::vector<u8> local_buffer(size + 20);
 
     buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
@@ -169,10 +173,8 @@ DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8
 }
 
 DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
-    std::vector<u8> output;
-
-    const std::vector<u8> mcu_state{static_cast<u8>(enable ? 1 : 0)};
-    const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state, output);
+    const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
+    const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
 
     if (result != DriverResult::Success) {
         LOG_ERROR(Input, "SendMCUData failed with error {}", result);
@@ -183,13 +185,11 @@ DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
 
 DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
     LOG_DEBUG(Input, "ConfigureMCU");
-    std::vector<u8> output;
-
     std::array<u8, sizeof(MCUConfig)> config_buffer;
     memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
     config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
 
-    const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer, output);
+    const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
 
     if (result != DriverResult::Success) {
         LOG_ERROR(Input, "Set MCU config failed with error {}", result);
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
index 2a3feaf598..903bcf4025 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.h
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -74,12 +74,19 @@ public:
      */
     DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
 
+    /**
+     * Sends a sub command to the device and waits for it's reply and ignores the output
+     * @param sc sub command to be send
+     * @param buffer data to be send
+     */
+    DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer);
+
     /**
      * Sends a mcu command to the device
      * @param sc sub command to be send
      * @param buffer data to be send
      */
-    DriverResult SendMcuCommand(SubCommand sc, std::span<const u8> buffer);
+    DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer);
 
     /**
      * Sends vibration data to the joycon
@@ -150,4 +157,17 @@ private:
     std::shared_ptr<JoyconHandle> hidapi_handle;
 };
 
+class ScopedSetBlocking {
+public:
+    explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} {
+        m_self->SetBlocking();
+    }
+
+    ~ScopedSetBlocking() {
+        m_self->SetNonBlocking();
+    }
+
+private:
+    JoyconCommonProtocol* m_self{};
+};
 } // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
index cbd9ff4f8b..52bb8b61aa 100644
--- a/src/input_common/helpers/joycon_protocol/generic_functions.cpp
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
@@ -10,22 +10,18 @@ GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
     : JoyconCommonProtocol(std::move(handle)) {}
 
 DriverResult GenericProtocol::EnablePassiveMode() {
-    SetBlocking();
-    const auto result = SetReportMode(ReportMode::SIMPLE_HID_MODE);
-    SetNonBlocking();
-    return result;
+    ScopedSetBlocking sb(this);
+    return SetReportMode(ReportMode::SIMPLE_HID_MODE);
 }
 
 DriverResult GenericProtocol::EnableActiveMode() {
-    SetBlocking();
-    const auto result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
-    SetNonBlocking();
-    return result;
+    ScopedSetBlocking sb(this);
+    return SetReportMode(ReportMode::STANDARD_FULL_60HZ);
 }
 
 DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> output;
-    SetBlocking();
 
     const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
 
@@ -34,7 +30,6 @@ DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
         memcpy(&device_info, output.data(), sizeof(DeviceInfo));
     }
 
-    SetNonBlocking();
     return result;
 }
 
@@ -43,36 +38,30 @@ DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type)
 }
 
 DriverResult GenericProtocol::EnableImu(bool enable) {
+    ScopedSetBlocking sb(this);
     const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
-    std::vector<u8> output;
-    SetBlocking();
-    const auto result = SendSubCommand(SubCommand::ENABLE_IMU, buffer, output);
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
 }
 
 DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
                                            AccelerometerSensitivity asen,
                                            AccelerometerPerformance afrec) {
+    ScopedSetBlocking sb(this);
     const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
                                    static_cast<u8>(gfrec), static_cast<u8>(afrec)};
-    std::vector<u8> output;
-    SetBlocking();
-    const auto result = SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer, output);
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer);
 }
 
 DriverResult GenericProtocol::GetBattery(u32& battery_level) {
+    // This function is meant to request the high resolution battery status
     battery_level = 0;
     return DriverResult::NotSupported;
 }
 
 DriverResult GenericProtocol::GetColor(Color& color) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
-    SetBlocking();
     const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
-    SetNonBlocking();
 
     color = {};
     if (result == DriverResult::Success) {
@@ -86,10 +75,9 @@ DriverResult GenericProtocol::GetColor(Color& color) {
 }
 
 DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
+    ScopedSetBlocking sb(this);
     std::vector<u8> buffer;
-    SetBlocking();
     const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
-    SetNonBlocking();
 
     serial_number = {};
     if (result == DriverResult::Success) {
@@ -115,14 +103,9 @@ DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
 }
 
 DriverResult GenericProtocol::SetHomeLight() {
+    ScopedSetBlocking sb(this);
     static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
-    std::vector<u8> output;
-    SetBlocking();
-
-    const auto result = SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer, output);
-
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer);
 }
 
 DriverResult GenericProtocol::SetLedBusy() {
@@ -130,14 +113,9 @@ DriverResult GenericProtocol::SetLedBusy() {
 }
 
 DriverResult GenericProtocol::SetLedPattern(u8 leds) {
+    ScopedSetBlocking sb(this);
     const std::array<u8, 1> buffer{leds};
-    std::vector<u8> output;
-    SetBlocking();
-
-    const auto result = SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer, output);
-
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer);
 }
 
 DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp
index 9dfa503c23..09e17bc5b6 100644
--- a/src/input_common/helpers/joycon_protocol/irs.cpp
+++ b/src/input_common/helpers/joycon_protocol/irs.cpp
@@ -12,8 +12,8 @@ IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
 
 DriverResult IrsProtocol::EnableIrs() {
     LOG_INFO(Input, "Enable IRS");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
@@ -49,14 +49,13 @@ DriverResult IrsProtocol::EnableIrs() {
 
     is_enabled = true;
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult IrsProtocol::DisableIrs() {
     LOG_DEBUG(Input, "Disable IRS");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = EnableMCU(false);
@@ -64,7 +63,6 @@ DriverResult IrsProtocol::DisableIrs() {
 
     is_enabled = false;
 
-    SetNonBlocking();
     return result;
 }
 
@@ -148,7 +146,7 @@ DriverResult IrsProtocol::ConfigureIrs() {
     };
     buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
 
-    std::vector<u8> request_data(sizeof(IrsConfigure));
+    std::array<u8, sizeof(IrsConfigure)> request_data{};
     memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     do {
@@ -191,7 +189,7 @@ DriverResult IrsProtocol::WriteRegistersStep1() {
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
+    std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
     memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
 
@@ -208,7 +206,7 @@ DriverResult IrsProtocol::WriteRegistersStep1() {
 
         // First time we need to set the report mode
         if (result == DriverResult::Success && tries == 0) {
-            result = SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+            result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
         }
         if (result == DriverResult::Success && tries == 0) {
             GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
@@ -250,7 +248,7 @@ DriverResult IrsProtocol::WriteRegistersStep2() {
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(IrsWriteRegisters));
+    std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
     memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     do {
@@ -272,7 +270,7 @@ DriverResult IrsProtocol::RequestFrame(u8 frame) {
     mcu_request[3] = frame;
     mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
     mcu_request[37] = 0xFF;
-    return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+    return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
 }
 
 DriverResult IrsProtocol::ResendFrame(u8 frame) {
@@ -282,7 +280,7 @@ DriverResult IrsProtocol::ResendFrame(u8 frame) {
     mcu_request[3] = 0x0;
     mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
     mcu_request[37] = 0xFF;
-    return SendMcuCommand(SubCommand::SET_REPORT_MODE, mcu_request);
+    return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
 }
 
 std::vector<u8> IrsProtocol::GetImage() const {
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
index 273c8d07d3..e2d47349f0 100644
--- a/src/input_common/helpers/joycon_protocol/joycon_types.h
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -273,6 +273,18 @@ enum class NFCTagType : u8 {
     Ntag215 = 0x01,
 };
 
+enum class NFCPages {
+    Block0 = 0,
+    Block45 = 45,
+    Block135 = 135,
+    Block231 = 231,
+};
+
+enum class NFCStatus : u8 {
+    LastPackage = 0x04,
+    TagLost = 0x07,
+};
+
 enum class IrsMode : u8 {
     None = 0x02,
     Moment = 0x03,
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
index 8755e310b8..5c0f717228 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.cpp
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -12,8 +12,8 @@ NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
 
 DriverResult NfcProtocol::EnableNfc() {
     LOG_INFO(Input, "Enable NFC");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
@@ -35,14 +35,13 @@ DriverResult NfcProtocol::EnableNfc() {
         result = ConfigureMCU(config);
     }
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult NfcProtocol::DisableNfc() {
     LOG_DEBUG(Input, "Disable NFC");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = EnableMCU(false);
@@ -50,15 +49,14 @@ DriverResult NfcProtocol::DisableNfc() {
 
     is_enabled = false;
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult NfcProtocol::StartNFCPollingMode() {
     LOG_DEBUG(Input, "Start NFC pooling Mode");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
     TagFoundData tag_data{};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
@@ -70,15 +68,14 @@ DriverResult NfcProtocol::StartNFCPollingMode() {
         is_enabled = true;
     }
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
     LOG_DEBUG(Input, "Start NFC pooling Mode");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
     TagFoundData tag_data{};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = StartPolling(tag_data);
@@ -96,20 +93,18 @@ DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
         result = GetAmiiboData(data);
     }
 
-    SetNonBlocking();
     return result;
 }
 
 bool NfcProtocol::HasAmiibo() {
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
     TagFoundData tag_data{};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = StartPolling(tag_data);
     }
 
-    SetNonBlocking();
     return result == DriverResult::Success;
 }
 
@@ -169,55 +164,53 @@ DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
     LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
 
     tries = 0;
-    std::size_t ntag_pages = 0;
+    NFCPages ntag_pages = NFCPages::Block0;
     // Read Tag data
-loop1:
     while (true) {
         auto result = SendReadAmiiboRequest(output, ntag_pages);
+        const auto mcu_report = static_cast<MCUReport>(output[49]);
+        const auto nfc_status = static_cast<NFCStatus>(output[56]);
 
-        int attempt = 0;
-        while (1) {
-            if (attempt != 0) {
-                result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
+        if (result != DriverResult::Success) {
+            return result;
+        }
+
+        if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
+            nfc_status == NFCStatus::TagLost) {
+            return DriverResult::ErrorReadingData;
+        }
+
+        if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) {
+            if (data.type != 2) {
+                continue;
             }
-            if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
+            switch (output[74]) {
+            case 0:
+                ntag_pages = NFCPages::Block135;
+                break;
+            case 3:
+                ntag_pages = NFCPages::Block45;
+                break;
+            case 4:
+                ntag_pages = NFCPages::Block231;
+                break;
+            default:
                 return DriverResult::ErrorReadingData;
             }
-            if (output[49] == 0x3a && output[51] == 0x07 && output[52] == 0x01) {
-                if (data.type != 2) {
-                    goto loop1;
-                }
-                switch (output[74]) {
-                case 0:
-                    ntag_pages = 135;
-                    break;
-                case 3:
-                    ntag_pages = 45;
-                    break;
-                case 4:
-                    ntag_pages = 231;
-                    break;
-                default:
-                    return DriverResult::ErrorReadingData;
-                }
-                goto loop1;
-            }
-            if (output[49] == 0x2a && output[56] == 0x04) {
-                // finished
-                SendStopPollingRequest(output);
-                return DriverResult::Success;
-            }
-            if (output[49] == 0x2a) {
-                goto loop1;
-            }
-            if (attempt++ > 6) {
-                goto loop1;
-            }
+            continue;
         }
 
-        if (result != DriverResult::Success) {
-            return result;
+        if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
+            // finished
+            SendStopPollingRequest(output);
+            return DriverResult::Success;
+        }
+
+        // Ignore other state reports
+        if (mcu_report == MCUReport::NFCState) {
+            continue;
         }
+
         if (tries++ > timeout_limit) {
             return DriverResult::Timeout;
         }
@@ -231,47 +224,44 @@ DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
     std::vector<u8> output;
     std::size_t tries = 0;
 
-    std::size_t ntag_pages = 135;
+    NFCPages ntag_pages = NFCPages::Block135;
     std::size_t ntag_buffer_pos = 0;
     // Read Tag data
-loop1:
     while (true) {
         auto result = SendReadAmiiboRequest(output, ntag_pages);
+        const auto mcu_report = static_cast<MCUReport>(output[49]);
+        const auto nfc_status = static_cast<NFCStatus>(output[56]);
 
-        int attempt = 0;
-        while (1) {
-            if (attempt != 0) {
-                result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
-            }
-            if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
-                return DriverResult::ErrorReadingData;
-            }
-            if (output[49] == 0x3a && output[51] == 0x07) {
-                std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
-                if (output[52] == 0x01) {
-                    memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116,
-                           payload_size - 60);
-                    ntag_buffer_pos += payload_size - 60;
-                } else {
-                    memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
-                }
-                goto loop1;
-            }
-            if (output[49] == 0x2a && output[56] == 0x04) {
-                LOG_INFO(Input, "Finished reading amiibo");
-                return DriverResult::Success;
-            }
-            if (output[49] == 0x2a) {
-                goto loop1;
-            }
-            if (attempt++ > 4) {
-                goto loop1;
+        if (result != DriverResult::Success) {
+            return result;
+        }
+
+        if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
+            nfc_status == NFCStatus::TagLost) {
+            return DriverResult::ErrorReadingData;
+        }
+
+        if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) {
+            std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
+            if (output[52] == 0x01) {
+                memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60);
+                ntag_buffer_pos += payload_size - 60;
+            } else {
+                memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
             }
+            continue;
         }
 
-        if (result != DriverResult::Success) {
-            return result;
+        if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
+            LOG_INFO(Input, "Finished reading amiibo");
+            return DriverResult::Success;
+        }
+
+        // Ignore other state reports
+        if (mcu_report == MCUReport::NFCState) {
+            continue;
         }
+
         if (tries++ > timeout_limit) {
             return DriverResult::Timeout;
         }
@@ -298,7 +288,7 @@ DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(NFCRequestState));
+    std::array<u8, sizeof(NFCRequestState)> request_data{};
     memcpy(request_data.data(), &request, sizeof(NFCRequestState));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
@@ -315,7 +305,7 @@ DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(NFCRequestState));
+    std::array<u8, sizeof(NFCRequestState)> request_data{};
     memcpy(request_data.data(), &request, sizeof(NFCRequestState));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
@@ -338,7 +328,7 @@ DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output
     return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
 }
 
-DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages) {
+DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) {
     NFCRequestState request{
         .sub_command = MCUSubCommand::ReadDeviceMode,
         .command_argument = NFCReadCommand::Ntag,
@@ -357,20 +347,19 @@ DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, std::si
         .crc = {},
     };
 
-    std::vector<u8> request_data(sizeof(NFCRequestState));
+    std::array<u8, sizeof(NFCRequestState)> request_data{};
     memcpy(request_data.data(), &request, sizeof(NFCRequestState));
     request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
     return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
 }
 
-NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
-    if (pages == 0) {
+NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
+    switch (pages) {
+    case NFCPages::Block0:
         return {
             .block_count = 1,
         };
-    }
-
-    if (pages == 45) {
+    case NFCPages::Block45:
         return {
             .block_count = 1,
             .blocks =
@@ -378,9 +367,7 @@ NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
                     NFCReadBlock{0x00, 0x2C},
                 },
         };
-    }
-
-    if (pages == 135) {
+    case NFCPages::Block135:
         return {
             .block_count = 3,
             .blocks =
@@ -390,9 +377,7 @@ NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
                     {0x78, 0x86},
                 },
         };
-    }
-
-    if (pages == 231) {
+    case NFCPages::Block231:
         return {
             .block_count = 4,
             .blocks =
@@ -403,9 +388,9 @@ NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
                     {0xb4, 0xe6},
                 },
         };
-    }
-
-    return {};
+    default:
+        return {};
+    };
 }
 
 bool NfcProtocol::IsEnabled() const {
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
index 5cb0e5a652..e63665aa97 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.h
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -51,9 +51,9 @@ private:
 
     DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
 
-    DriverResult SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages);
+    DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages);
 
-    NFCReadBlockCommand GetReadBlockCommand(std::size_t pages) const;
+    NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
 
     bool is_enabled{};
 };
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index 940b20b7f0..7f8e093faf 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -224,9 +224,9 @@ void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
         Joycon::PasivePadButton::StickL,
     };
 
-    for (std::size_t i = 0; i < left_buttons.size(); ++i) {
-        const bool button_status = (input.button_input & static_cast<u32>(left_buttons[i])) != 0;
-        const int button = static_cast<int>(left_buttons[i]);
+    for (auto left_button : left_buttons) {
+        const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
+        const int button = static_cast<int>(left_button);
         callbacks.on_button_data(button, button_status);
     }
 }
@@ -241,9 +241,9 @@ void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
         Joycon::PasivePadButton::StickR,
     };
 
-    for (std::size_t i = 0; i < right_buttons.size(); ++i) {
-        const bool button_status = (input.button_input & static_cast<u32>(right_buttons[i])) != 0;
-        const int button = static_cast<int>(right_buttons[i]);
+    for (auto right_button : right_buttons) {
+        const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
+        const int button = static_cast<int>(right_button);
         callbacks.on_button_data(button, button_status);
     }
 }
@@ -259,9 +259,9 @@ void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
         Joycon::PasivePadButton::StickL,  Joycon::PasivePadButton::StickR,
     };
 
-    for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
-        const bool button_status = (input.button_input & static_cast<u32>(pro_buttons[i])) != 0;
-        const int button = static_cast<int>(pro_buttons[i]);
+    for (auto pro_button : pro_buttons) {
+        const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
+        const int button = static_cast<int>(pro_button);
         callbacks.on_button_data(button, button_status);
     }
 }
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
index 8adad57dd6..12f81309e1 100644
--- a/src/input_common/helpers/joycon_protocol/ringcon.cpp
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -11,8 +11,8 @@ RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
 
 DriverResult RingConProtocol::EnableRingCon() {
     LOG_DEBUG(Input, "Enable Ringcon");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
@@ -30,14 +30,13 @@ DriverResult RingConProtocol::EnableRingCon() {
         result = ConfigureMCU(config);
     }
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult RingConProtocol::DisableRingCon() {
     LOG_DEBUG(Input, "Disable RingCon");
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
 
     if (result == DriverResult::Success) {
         result = EnableMCU(false);
@@ -45,15 +44,14 @@ DriverResult RingConProtocol::DisableRingCon() {
 
     is_enabled = false;
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult RingConProtocol::StartRingconPolling() {
     LOG_DEBUG(Input, "Enable Ringcon");
-    bool is_connected = false;
+    ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
-    SetBlocking();
+    bool is_connected = false;
 
     if (result == DriverResult::Success) {
         result = IsRingConnected(is_connected);
@@ -66,13 +64,13 @@ DriverResult RingConProtocol::StartRingconPolling() {
         is_enabled = true;
     }
 
-    SetNonBlocking();
     return result;
 }
 
 DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
     LOG_DEBUG(Input, "IsRingConnected");
     constexpr std::size_t max_tries = 28;
+    constexpr u8 ring_controller_id = 0x20;
     std::vector<u8> output;
     std::size_t tries = 0;
     is_connected = false;
@@ -88,7 +86,7 @@ DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
         if (tries++ >= max_tries) {
             return DriverResult::NoDeviceDetected;
         }
-    } while (output[14] != 0x59 || output[16] != 0x20);
+    } while (output[16] != ring_controller_id);
 
     is_connected = true;
     return DriverResult::Success;
@@ -96,30 +94,20 @@ DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
 
 DriverResult RingConProtocol::ConfigureRing() {
     LOG_DEBUG(Input, "ConfigureRing");
-    constexpr std::size_t max_tries = 28;
-    DriverResult result{DriverResult::Success};
-    std::vector<u8> output;
-    std::size_t tries = 0;
 
     static constexpr std::array<u8, 37> ring_config{
         0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
         0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
-    do {
-        result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config, output);
 
-        if (result != DriverResult::Success) {
-            return result;
-        }
-        if (tries++ >= max_tries) {
-            return DriverResult::NoDeviceDetected;
-        }
-    } while (output[14] != 0x5C);
+    const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config);
 
-    static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
-    result = SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data, output);
+    if (result != DriverResult::Success) {
+        return result;
+    }
 
-    return result;
+    static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
+    return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data);
 }
 
 bool RingConProtocol::IsEnabled() const {
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
index fad67a94ba..63b60c9468 100644
--- a/src/input_common/helpers/joycon_protocol/rumble.cpp
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -14,12 +14,9 @@ RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
 
 DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
     LOG_DEBUG(Input, "Enable Rumble");
+    ScopedSetBlocking sb(this);
     const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
-    std::vector<u8> output;
-    SetBlocking();
-    const auto result = SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer, output);
-    SetNonBlocking();
-    return result;
+    return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
 }
 
 DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
@@ -66,9 +63,9 @@ u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
 }
 
 u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
-    /* More information about these values can be found here:
-     * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
-     */
+    // More information about these values can be found here:
+    // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+
     static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
         std::pair<f32, int>{0.0f, 0x0},
         {0.01f, 0x2},
@@ -183,9 +180,9 @@ u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
 }
 
 u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
-    /* More information about these values can be found here:
-     * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
-     */
+    // More information about these values can be found here:
+    // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+
     static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
         std::pair<f32, int>{0.0f, 0x0040},
         {0.01f, 0x8040},
-- 
cgit v1.2.3-70-g09d2