summaryrefslogtreecommitdiffstats
path: root/src/input_common/helpers/joycon_protocol
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/helpers/joycon_protocol')
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp13
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.h7
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.cpp300
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.h63
-rw-r--r--src/input_common/helpers/joycon_protocol/joycon_types.h107
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.cpp6
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.h3
7 files changed, 496 insertions, 3 deletions
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index a4d08fdaf..a329db107 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 a65e4aa76..2a3feaf59 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.h
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -75,6 +75,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 000000000..9dfa503c2
--- /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 000000000..76dfa02ea
--- /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 36c00a8d7..273c8d07d 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 fd05d98f3..940b20b7f 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 c40fc7bca..354d41dad 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,