summaryrefslogtreecommitdiff
path: root/src/input_common/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/helpers')
-rw-r--r--src/input_common/helpers/joycon_driver.cpp575
-rw-r--r--src/input_common/helpers/joycon_driver.h150
-rw-r--r--src/input_common/helpers/joycon_protocol/calibration.cpp184
-rw-r--r--src/input_common/helpers/joycon_protocol/calibration.h64
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.h173
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.cpp136
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.h114
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.cpp298
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.h63
-rw-r--r--src/input_common/helpers/joycon_protocol/joycon_types.h613
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.cpp400
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.h61
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.cpp341
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.h81
-rw-r--r--src/input_common/helpers/joycon_protocol/ringcon.cpp117
-rw-r--r--src/input_common/helpers/joycon_protocol/ringcon.h38
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.h33
-rw-r--r--src/input_common/helpers/stick_from_buttons.cpp9
20 files changed, 4046 insertions, 2 deletions
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
new file mode 100644
index 000000000..3775e2d35
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,575 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "common/swap.h"
6#include "common/thread.h"
7#include "input_common/helpers/joycon_driver.h"
8#include "input_common/helpers/joycon_protocol/calibration.h"
9#include "input_common/helpers/joycon_protocol/generic_functions.h"
10#include "input_common/helpers/joycon_protocol/irs.h"
11#include "input_common/helpers/joycon_protocol/nfc.h"
12#include "input_common/helpers/joycon_protocol/poller.h"
13#include "input_common/helpers/joycon_protocol/ringcon.h"
14#include "input_common/helpers/joycon_protocol/rumble.h"
15
16namespace InputCommon::Joycon {
17JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
18 hidapi_handle = std::make_shared<JoyconHandle>();
19}
20
21JoyconDriver::~JoyconDriver() {
22 Stop();
23}
24
25void JoyconDriver::Stop() {
26 is_connected = false;
27 input_thread = {};
28}
29
30DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
31 std::scoped_lock lock{mutex};
32
33 handle_device_type = ControllerType::None;
34 GetDeviceType(device_info, handle_device_type);
35 if (handle_device_type == ControllerType::None) {
36 return DriverResult::UnsupportedControllerType;
37 }
38
39 hidapi_handle->handle =
40 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
41 std::memcpy(&handle_serial_number, device_info->serial_number, 15);
42 if (!hidapi_handle->handle) {
43 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
44 device_info->vendor_id, device_info->product_id);
45 return DriverResult::HandleInUse;
46 }
47 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
48 return DriverResult::Success;
49}
50
51DriverResult JoyconDriver::InitializeDevice() {
52 if (!hidapi_handle->handle) {
53 return DriverResult::InvalidHandle;
54 }
55 std::scoped_lock lock{mutex};
56 disable_input_thread = true;
57
58 // Reset Counters
59 error_counter = 0;
60 hidapi_handle->packet_counter = 0;
61
62 // Reset external device status
63 starlink_connected = false;
64 ring_connected = false;
65 amiibo_detected = false;
66
67 // Set HW default configuration
68 vibration_enabled = true;
69 motion_enabled = true;
70 hidbus_enabled = false;
71 nfc_enabled = false;
72 passive_enabled = false;
73 irs_enabled = false;
74 gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
75 gyro_performance = Joycon::GyroPerformance::HZ833;
76 accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
77 accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
78
79 // Initialize HW Protocols
80 calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
81 generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
82 irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
83 nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
84 ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
85 rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
86
87 // Get fixed joycon info
88 generic_protocol->GetVersionNumber(version);
89 generic_protocol->SetLowPowerMode(false);
90 generic_protocol->GetColor(color);
91 if (handle_device_type == ControllerType::Pro) {
92 // Some 3rd party controllers aren't pro controllers
93 generic_protocol->GetControllerType(device_type);
94 } else {
95 device_type = handle_device_type;
96 }
97 generic_protocol->GetSerialNumber(serial_number);
98 supported_features = GetSupportedFeatures();
99
100 // Get Calibration data
101 calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
102 calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
103 calibration_protocol->GetImuCalibration(motion_calibration);
104
105 // Set led status
106 generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
107
108 // Apply HW configuration
109 SetPollingMode();
110
111 // Initialize joycon poller
112 joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
113 right_stick_calibration, motion_calibration);
114
115 // Start pooling for data
116 is_connected = true;
117 if (!input_thread_running) {
118 input_thread =
119 std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
120 }
121
122 disable_input_thread = false;
123 return DriverResult::Success;
124}
125
126void JoyconDriver::InputThread(std::stop_token stop_token) {
127 LOG_INFO(Input, "Joycon Adapter input thread started");
128 Common::SetCurrentThreadName("JoyconInput");
129 input_thread_running = true;
130
131 // Max update rate is 5ms, ensure we are always able to read a bit faster
132 constexpr int ThreadDelay = 2;
133 std::vector<u8> buffer(MaxBufferSize);
134
135 while (!stop_token.stop_requested()) {
136 int status = 0;
137
138 if (!IsInputThreadValid()) {
139 input_thread.request_stop();
140 continue;
141 }
142
143 // By disabling the input thread we can ensure custom commands will succeed as no package is
144 // skipped
145 if (!disable_input_thread) {
146 status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
147 ThreadDelay);
148 } else {
149 std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
150 }
151
152 if (IsPayloadCorrect(status, buffer)) {
153 OnNewData(buffer);
154 }
155
156 std::this_thread::yield();
157 }
158
159 is_connected = false;
160 input_thread_running = false;
161 LOG_INFO(Input, "Joycon Adapter input thread stopped");
162}
163
164void JoyconDriver::OnNewData(std::span<u8> buffer) {
165 const auto report_mode = static_cast<InputReport>(buffer[0]);
166
167 // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
168 // experience
169 switch (report_mode) {
170 case InputReport::STANDARD_FULL_60HZ:
171 case InputReport::NFC_IR_MODE_60HZ:
172 case InputReport::SIMPLE_HID_MODE: {
173 const auto now = std::chrono::steady_clock::now();
174 const auto new_delta_time = static_cast<u64>(
175 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
176 delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
177 last_update = now;
178 joycon_poller->UpdateColor(color);
179 break;
180 }
181 default:
182 break;
183 }
184
185 const MotionStatus motion_status{
186 .is_enabled = motion_enabled,
187 .delta_time = delta_time,
188 .gyro_sensitivity = gyro_sensitivity,
189 .accelerometer_sensitivity = accelerometer_sensitivity,
190 };
191
192 // TODO: Remove this when calibration is properly loaded and not calculated
193 if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) {
194 InputReportActive data{};
195 memcpy(&data, buffer.data(), sizeof(InputReportActive));
196 calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
197 }
198
199 const RingStatus ring_status{
200 .is_enabled = ring_connected,
201 .default_value = ring_calibration.default_value,
202 .max_value = ring_calibration.max_value,
203 .min_value = ring_calibration.min_value,
204 };
205
206 if (irs_protocol->IsEnabled()) {
207 irs_protocol->RequestImage(buffer);
208 joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
209 }
210
211 if (nfc_protocol->IsEnabled()) {
212 if (amiibo_detected) {
213 if (!nfc_protocol->HasAmiibo()) {
214 joycon_poller->UpdateAmiibo({});
215 amiibo_detected = false;
216 return;
217 }
218 }
219
220 if (!amiibo_detected) {
221 std::vector<u8> data(0x21C);
222 const auto result = nfc_protocol->ScanAmiibo(data);
223 if (result == DriverResult::Success) {
224 joycon_poller->UpdateAmiibo(data);
225 amiibo_detected = true;
226 }
227 }
228 }
229
230 switch (report_mode) {
231 case InputReport::STANDARD_FULL_60HZ:
232 joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
233 break;
234 case InputReport::NFC_IR_MODE_60HZ:
235 joycon_poller->ReadNfcIRMode(buffer, motion_status);
236 break;
237 case InputReport::SIMPLE_HID_MODE:
238 joycon_poller->ReadPassiveMode(buffer);
239 break;
240 case InputReport::SUBCMD_REPLY:
241 LOG_DEBUG(Input, "Unhandled command reply");
242 break;
243 default:
244 LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
245 break;
246 }
247}
248
249DriverResult JoyconDriver::SetPollingMode() {
250 disable_input_thread = true;
251
252 rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
253
254 if (motion_enabled && supported_features.motion) {
255 generic_protocol->EnableImu(true);
256 generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
257 accelerometer_sensitivity, accelerometer_performance);
258 } else {
259 generic_protocol->EnableImu(false);
260 }
261
262 if (irs_protocol->IsEnabled()) {
263 irs_protocol->DisableIrs();
264 }
265
266 if (nfc_protocol->IsEnabled()) {
267 amiibo_detected = false;
268 nfc_protocol->DisableNfc();
269 }
270
271 if (ring_protocol->IsEnabled()) {
272 ring_connected = false;
273 ring_protocol->DisableRingCon();
274 }
275
276 if (irs_enabled && supported_features.irs) {
277 auto result = irs_protocol->EnableIrs();
278 if (result == DriverResult::Success) {
279 disable_input_thread = false;
280 return result;
281 }
282 irs_protocol->DisableIrs();
283 LOG_ERROR(Input, "Error enabling IRS");
284 }
285
286 if (nfc_enabled && supported_features.nfc) {
287 auto result = nfc_protocol->EnableNfc();
288 if (result == DriverResult::Success) {
289 result = nfc_protocol->StartNFCPollingMode();
290 }
291 if (result == DriverResult::Success) {
292 disable_input_thread = false;
293 return result;
294 }
295 nfc_protocol->DisableNfc();
296 LOG_ERROR(Input, "Error enabling NFC");
297 }
298
299 if (hidbus_enabled && supported_features.hidbus) {
300 auto result = ring_protocol->EnableRingCon();
301 if (result == DriverResult::Success) {
302 result = ring_protocol->StartRingconPolling();
303 }
304 if (result == DriverResult::Success) {
305 ring_connected = true;
306 disable_input_thread = false;
307 return result;
308 }
309 ring_connected = false;
310 ring_protocol->DisableRingCon();
311 LOG_ERROR(Input, "Error enabling Ringcon");
312 }
313
314 if (passive_enabled && supported_features.passive) {
315 const auto result = generic_protocol->EnablePassiveMode();
316 if (result == DriverResult::Success) {
317 disable_input_thread = false;
318 return result;
319 }
320 LOG_ERROR(Input, "Error enabling passive mode");
321 }
322
323 // Default Mode
324 const auto result = generic_protocol->EnableActiveMode();
325 if (result != DriverResult::Success) {
326 LOG_ERROR(Input, "Error enabling active mode");
327 }
328 // Switch calls this function after enabling active mode
329 generic_protocol->TriggersElapsed();
330
331 disable_input_thread = false;
332 return result;
333}
334
335JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
336 SupportedFeatures features{
337 .passive = true,
338 .motion = true,
339 .vibration = true,
340 };
341
342 if (device_type == ControllerType::Right) {
343 features.nfc = true;
344 features.irs = true;
345 features.hidbus = true;
346 }
347
348 if (device_type == ControllerType::Pro) {
349 features.nfc = true;
350 }
351 return features;
352}
353
354bool JoyconDriver::IsInputThreadValid() const {
355 if (!is_connected.load()) {
356 return false;
357 }
358 if (hidapi_handle->handle == nullptr) {
359 return false;
360 }
361 // Controller is not responding. Terminate connection
362 if (error_counter > MaxErrorCount) {
363 return false;
364 }
365 return true;
366}
367
368bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
369 if (status <= -1) {
370 error_counter++;
371 return false;
372 }
373 // There's no new data
374 if (status == 0) {
375 return false;
376 }
377 // No reply ever starts with zero
378 if (buffer[0] == 0x00) {
379 error_counter++;
380 return false;
381 }
382 error_counter = 0;
383 return true;
384}
385
386DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
387 std::scoped_lock lock{mutex};
388 if (disable_input_thread) {
389 return DriverResult::HandleInUse;
390 }
391 return rumble_protocol->SendVibration(vibration);
392}
393
394DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
395 std::scoped_lock lock{mutex};
396 if (disable_input_thread) {
397 return DriverResult::HandleInUse;
398 }
399 return generic_protocol->SetLedPattern(led_pattern);
400}
401
402DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
403 std::scoped_lock lock{mutex};
404 if (disable_input_thread) {
405 return DriverResult::HandleInUse;
406 }
407 disable_input_thread = true;
408 const auto result = irs_protocol->SetIrsConfig(mode_, format_);
409 disable_input_thread = false;
410 return result;
411}
412
413DriverResult JoyconDriver::SetPasiveMode() {
414 std::scoped_lock lock{mutex};
415 motion_enabled = false;
416 hidbus_enabled = false;
417 nfc_enabled = false;
418 passive_enabled = true;
419 irs_enabled = false;
420 return SetPollingMode();
421}
422
423DriverResult JoyconDriver::SetActiveMode() {
424 if (is_ring_disabled_by_irs) {
425 is_ring_disabled_by_irs = false;
426 SetActiveMode();
427 return SetRingConMode();
428 }
429
430 std::scoped_lock lock{mutex};
431 motion_enabled = true;
432 hidbus_enabled = false;
433 nfc_enabled = false;
434 passive_enabled = false;
435 irs_enabled = false;
436 return SetPollingMode();
437}
438
439DriverResult JoyconDriver::SetIrMode() {
440 std::scoped_lock lock{mutex};
441
442 if (!supported_features.irs) {
443 return DriverResult::NotSupported;
444 }
445
446 if (ring_connected) {
447 is_ring_disabled_by_irs = true;
448 }
449
450 motion_enabled = false;
451 hidbus_enabled = false;
452 nfc_enabled = false;
453 passive_enabled = false;
454 irs_enabled = true;
455 return SetPollingMode();
456}
457
458DriverResult JoyconDriver::SetNfcMode() {
459 std::scoped_lock lock{mutex};
460
461 if (!supported_features.nfc) {
462 return DriverResult::NotSupported;
463 }
464
465 motion_enabled = true;
466 hidbus_enabled = false;
467 nfc_enabled = true;
468 passive_enabled = false;
469 irs_enabled = false;
470 return SetPollingMode();
471}
472
473DriverResult JoyconDriver::SetRingConMode() {
474 std::scoped_lock lock{mutex};
475
476 if (!supported_features.hidbus) {
477 return DriverResult::NotSupported;
478 }
479
480 motion_enabled = true;
481 hidbus_enabled = true;
482 nfc_enabled = false;
483 passive_enabled = false;
484 irs_enabled = false;
485
486 const auto result = SetPollingMode();
487
488 if (!ring_connected) {
489 return DriverResult::NoDeviceDetected;
490 }
491
492 return result;
493}
494
495bool JoyconDriver::IsConnected() const {
496 std::scoped_lock lock{mutex};
497 return is_connected.load();
498}
499
500bool JoyconDriver::IsVibrationEnabled() const {
501 std::scoped_lock lock{mutex};
502 return vibration_enabled;
503}
504
505FirmwareVersion JoyconDriver::GetDeviceVersion() const {
506 std::scoped_lock lock{mutex};
507 return version;
508}
509
510Color JoyconDriver::GetDeviceColor() const {
511 std::scoped_lock lock{mutex};
512 return color;
513}
514
515std::size_t JoyconDriver::GetDevicePort() const {
516 std::scoped_lock lock{mutex};
517 return port;
518}
519
520ControllerType JoyconDriver::GetDeviceType() const {
521 std::scoped_lock lock{mutex};
522 return device_type;
523}
524
525ControllerType JoyconDriver::GetHandleDeviceType() const {
526 std::scoped_lock lock{mutex};
527 return handle_device_type;
528}
529
530SerialNumber JoyconDriver::GetSerialNumber() const {
531 std::scoped_lock lock{mutex};
532 return serial_number;
533}
534
535SerialNumber JoyconDriver::GetHandleSerialNumber() const {
536 std::scoped_lock lock{mutex};
537 return handle_serial_number;
538}
539
540void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
541 joycon_poller->SetCallbacks(callbacks);
542}
543
544DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
545 ControllerType& controller_type) {
546 static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{
547 std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
548 {0x2007, ControllerType::Right},
549 };
550 constexpr u16 nintendo_vendor_id = 0x057e;
551
552 controller_type = ControllerType::None;
553 if (device_info->vendor_id != nintendo_vendor_id) {
554 return DriverResult::UnsupportedControllerType;
555 }
556
557 for (const auto& [product_id, type] : supported_devices) {
558 if (device_info->product_id == static_cast<u16>(product_id)) {
559 controller_type = type;
560 return Joycon::DriverResult::Success;
561 }
562 }
563 return Joycon::DriverResult::UnsupportedControllerType;
564}
565
566DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
567 SerialNumber& serial_number) {
568 if (device_info->serial_number == nullptr) {
569 return DriverResult::Unknown;
570 }
571 std::memcpy(&serial_number, device_info->serial_number, 15);
572 return Joycon::DriverResult::Success;
573}
574
575} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
new file mode 100644
index 000000000..c1e189fa5
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.h
@@ -0,0 +1,150 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <functional>
8#include <mutex>
9#include <span>
10#include <thread>
11
12#include "input_common/helpers/joycon_protocol/joycon_types.h"
13
14namespace InputCommon::Joycon {
15class CalibrationProtocol;
16class GenericProtocol;
17class IrsProtocol;
18class NfcProtocol;
19class JoyconPoller;
20class RingConProtocol;
21class RumbleProtocol;
22
23class JoyconDriver final {
24public:
25 explicit JoyconDriver(std::size_t port_);
26
27 ~JoyconDriver();
28
29 DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
30 DriverResult InitializeDevice();
31 void Stop();
32
33 bool IsConnected() const;
34 bool IsVibrationEnabled() const;
35
36 FirmwareVersion GetDeviceVersion() const;
37 Color GetDeviceColor() const;
38 std::size_t GetDevicePort() const;
39 ControllerType GetDeviceType() const;
40 ControllerType GetHandleDeviceType() const;
41 SerialNumber GetSerialNumber() const;
42 SerialNumber GetHandleSerialNumber() const;
43
44 DriverResult SetVibration(const VibrationValue& vibration);
45 DriverResult SetLedConfig(u8 led_pattern);
46 DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
47 DriverResult SetPasiveMode();
48 DriverResult SetActiveMode();
49 DriverResult SetIrMode();
50 DriverResult SetNfcMode();
51 DriverResult SetRingConMode();
52
53 void SetCallbacks(const JoyconCallbacks& callbacks);
54
55 // Returns device type from hidapi handle
56 static DriverResult GetDeviceType(SDL_hid_device_info* device_info,
57 ControllerType& controller_type);
58
59 // Returns serial number from hidapi handle
60 static DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
61 SerialNumber& serial_number);
62
63private:
64 struct SupportedFeatures {
65 bool passive{};
66 bool hidbus{};
67 bool irs{};
68 bool motion{};
69 bool nfc{};
70 bool vibration{};
71 };
72
73 /// Main thread, actively request new data from the handle
74 void InputThread(std::stop_token stop_token);
75
76 /// Called everytime a valid package arrives
77 void OnNewData(std::span<u8> buffer);
78
79 /// Updates device configuration to enable or disable features
80 DriverResult SetPollingMode();
81
82 /// Returns true if input thread is valid and doesn't need to be stopped
83 bool IsInputThreadValid() const;
84
85 /// Returns true if the data should be interpreted. Otherwise the error counter is incremented
86 bool IsPayloadCorrect(int status, std::span<const u8> buffer);
87
88 /// Returns a list of supported features that can be enabled on this device
89 SupportedFeatures GetSupportedFeatures();
90
91 // Protocol Features
92 std::unique_ptr<CalibrationProtocol> calibration_protocol;
93 std::unique_ptr<GenericProtocol> generic_protocol;
94 std::unique_ptr<IrsProtocol> irs_protocol;
95 std::unique_ptr<NfcProtocol> nfc_protocol;
96 std::unique_ptr<JoyconPoller> joycon_poller;
97 std::unique_ptr<RingConProtocol> ring_protocol;
98 std::unique_ptr<RumbleProtocol> rumble_protocol;
99
100 // Connection status
101 std::atomic<bool> is_connected{};
102 u64 delta_time;
103 std::size_t error_counter{};
104 std::shared_ptr<JoyconHandle> hidapi_handle;
105 std::chrono::time_point<std::chrono::steady_clock> last_update;
106
107 // External device status
108 bool starlink_connected{};
109 bool ring_connected{};
110 bool amiibo_detected{};
111 bool is_ring_disabled_by_irs{};
112
113 // Harware configuration
114 u8 leds{};
115 ReportMode mode{};
116 bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
117 bool hidbus_enabled{}; // External device support
118 bool irs_enabled{}; // Infrared camera input
119 bool motion_enabled{}; // Enables motion input
120 bool nfc_enabled{}; // Enables Amiibo detection
121 bool vibration_enabled{}; // Allows vibrations
122
123 // Calibration data
124 GyroSensitivity gyro_sensitivity{};
125 GyroPerformance gyro_performance{};
126 AccelerometerSensitivity accelerometer_sensitivity{};
127 AccelerometerPerformance accelerometer_performance{};
128 JoyStickCalibration left_stick_calibration{};
129 JoyStickCalibration right_stick_calibration{};
130 MotionCalibration motion_calibration{};
131 RingCalibration ring_calibration{};
132
133 // Fixed joycon info
134 FirmwareVersion version{};
135 Color color{};
136 std::size_t port{};
137 ControllerType device_type{}; // Device type reported by controller
138 ControllerType handle_device_type{}; // Device type reported by hidapi
139 SerialNumber serial_number{}; // Serial number reported by controller
140 SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
141 SupportedFeatures supported_features{};
142
143 // Thread related
144 mutable std::mutex mutex;
145 std::jthread input_thread;
146 bool input_thread_running{};
147 bool disable_input_thread{};
148};
149
150} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
new file mode 100644
index 000000000..f6e7e97d5
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -0,0 +1,184 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cstring>
5
6#include "input_common/helpers/joycon_protocol/calibration.h"
7#include "input_common/helpers/joycon_protocol/joycon_types.h"
8
9namespace InputCommon::Joycon {
10
11CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
12 : JoyconCommonProtocol(std::move(handle)) {}
13
14DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
15 ScopedSetBlocking sb(this);
16 std::vector<u8> buffer;
17 DriverResult result{DriverResult::Success};
18 calibration = {};
19
20 result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
21
22 if (result == DriverResult::Success) {
23 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
24 if (has_user_calibration) {
25 result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer);
26 } else {
27 result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer);
28 }
29 }
30
31 if (result == DriverResult::Success) {
32 calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
33 calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
34 calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
35 calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
36 calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
37 calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
38 }
39
40 // Nintendo fix for drifting stick
41 // result = ReadSPI(0x60, 0x86 ,buffer, 16);
42 // calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
43
44 // Set a valid default calibration if data is missing
45 ValidateCalibration(calibration);
46
47 return result;
48}
49
50DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
51 ScopedSetBlocking sb(this);
52 std::vector<u8> buffer;
53 DriverResult result{DriverResult::Success};
54 calibration = {};
55
56 result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
57
58 if (result == DriverResult::Success) {
59 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
60 if (has_user_calibration) {
61 result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer);
62 } else {
63 result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer);
64 }
65 }
66
67 if (result == DriverResult::Success) {
68 calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
69 calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
70 calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
71 calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
72 calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
73 calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
74 }
75
76 // Nintendo fix for drifting stick
77 // buffer = ReadSPI(0x60, 0x98 , 16);
78 // joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
79
80 // Set a valid default calibration if data is missing
81 ValidateCalibration(calibration);
82
83 return result;
84}
85
86DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
87 ScopedSetBlocking sb(this);
88 std::vector<u8> buffer;
89 DriverResult result{DriverResult::Success};
90 calibration = {};
91
92 result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
93
94 if (result == DriverResult::Success) {
95 const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
96 if (has_user_calibration) {
97 result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer);
98 } else {
99 result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer);
100 }
101 }
102
103 if (result == DriverResult::Success) {
104 IMUCalibration device_calibration{};
105 memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration));
106 calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0];
107 calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1];
108 calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2];
109
110 calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0];
111 calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1];
112 calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2];
113
114 calibration.gyro[0].offset = device_calibration.gyroscope_offset[0];
115 calibration.gyro[1].offset = device_calibration.gyroscope_offset[1];
116 calibration.gyro[2].offset = device_calibration.gyroscope_offset[2];
117
118 calibration.gyro[0].scale = device_calibration.gyroscope_scale[0];
119 calibration.gyro[1].scale = device_calibration.gyroscope_scale[1];
120 calibration.gyro[2].scale = device_calibration.gyroscope_scale[2];
121 }
122
123 ValidateCalibration(calibration);
124
125 return result;
126}
127
128DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
129 s16 current_value) {
130 // TODO: Get default calibration form ring itself
131 if (ring_data_max == 0 && ring_data_min == 0) {
132 ring_data_max = current_value + 800;
133 ring_data_min = current_value - 800;
134 ring_data_default = current_value;
135 }
136 ring_data_max = std::max(ring_data_max, current_value);
137 ring_data_min = std::min(ring_data_min, current_value);
138 calibration = {
139 .default_value = ring_data_default,
140 .max_value = ring_data_max,
141 .min_value = ring_data_min,
142 };
143 return DriverResult::Success;
144}
145
146void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
147 constexpr u16 DefaultStickCenter{2048};
148 constexpr u16 DefaultStickRange{1740};
149
150 if (calibration.x.center == 0xFFF || calibration.x.center == 0) {
151 calibration.x.center = DefaultStickCenter;
152 }
153 if (calibration.x.max == 0xFFF || calibration.x.max == 0) {
154 calibration.x.max = DefaultStickRange;
155 }
156 if (calibration.x.min == 0xFFF || calibration.x.min == 0) {
157 calibration.x.min = DefaultStickRange;
158 }
159
160 if (calibration.y.center == 0xFFF || calibration.y.center == 0) {
161 calibration.y.center = DefaultStickCenter;
162 }
163 if (calibration.y.max == 0xFFF || calibration.y.max == 0) {
164 calibration.y.max = DefaultStickRange;
165 }
166 if (calibration.y.min == 0xFFF || calibration.y.min == 0) {
167 calibration.y.min = DefaultStickRange;
168 }
169}
170
171void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
172 for (auto& sensor : calibration.accelerometer) {
173 if (sensor.scale == 0) {
174 sensor.scale = 0x4000;
175 }
176 }
177 for (auto& sensor : calibration.gyro) {
178 if (sensor.scale == 0) {
179 sensor.scale = 0x3be7;
180 }
181 }
182}
183
184} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h
new file mode 100644
index 000000000..afb52a36a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.h
@@ -0,0 +1,64 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14
15namespace InputCommon::Joycon {
16enum class DriverResult;
17struct JoyStickCalibration;
18struct IMUCalibration;
19struct JoyconHandle;
20} // namespace InputCommon::Joycon
21
22namespace InputCommon::Joycon {
23
24/// Driver functions related to retrieving calibration data from the device
25class CalibrationProtocol final : private JoyconCommonProtocol {
26public:
27 explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
28
29 /**
30 * Sends a request to obtain the left stick calibration from memory
31 * @param is_factory_calibration if true factory values will be returned
32 * @returns JoyStickCalibration of the left joystick
33 */
34 DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);
35
36 /**
37 * Sends a request to obtain the right stick calibration from memory
38 * @param is_factory_calibration if true factory values will be returned
39 * @returns JoyStickCalibration of the right joystick
40 */
41 DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);
42
43 /**
44 * Sends a request to obtain the motion calibration from memory
45 * @returns ImuCalibration of the motion sensor
46 */
47 DriverResult GetImuCalibration(MotionCalibration& calibration);
48
49 /**
50 * Calculates on run time the proper calibration of the ring controller
51 * @returns RingCalibration of the ring sensor
52 */
53 DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
54
55private:
56 void ValidateCalibration(JoyStickCalibration& calibration);
57 void ValidateCalibration(MotionCalibration& calibration);
58
59 s16 ring_data_max = 0;
60 s16 ring_data_default = 0;
61 s16 ring_data_min = 0;
62};
63
64} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
new file mode 100644
index 000000000..417d0dcc5
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/common_protocol.h"
6
7namespace InputCommon::Joycon {
8JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
9 : hidapi_handle{std::move(hidapi_handle_)} {}
10
11u8 JoyconCommonProtocol::GetCounter() {
12 hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
13 return hidapi_handle->packet_counter;
14}
15
16void JoyconCommonProtocol::SetBlocking() {
17 SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
18}
19
20void JoyconCommonProtocol::SetNonBlocking() {
21 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
22}
23
24DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
25 std::vector<u8> buffer;
26 const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer);
27 controller_type = ControllerType::None;
28
29 if (result == DriverResult::Success) {
30 controller_type = static_cast<ControllerType>(buffer[0]);
31 // Fallback to 3rd party pro controllers
32 if (controller_type == ControllerType::None) {
33 controller_type = ControllerType::Pro;
34 }
35 }
36
37 return result;
38}
39
40DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
41 ControllerType controller_type{ControllerType::None};
42 const auto result = GetDeviceType(controller_type);
43 if (result != DriverResult::Success || controller_type == ControllerType::None) {
44 return DriverResult::UnsupportedControllerType;
45 }
46
47 hidapi_handle->handle =
48 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
49
50 if (!hidapi_handle->handle) {
51 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
52 device_info->vendor_id, device_info->product_id);
53 return DriverResult::HandleInUse;
54 }
55
56 SetNonBlocking();
57 return DriverResult::Success;
58}
59
60DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
61 const std::array<u8, 1> buffer{static_cast<u8>(report_mode)};
62 return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer);
63}
64
65DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
66 const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
67
68 if (result == -1) {
69 return DriverResult::ErrorWritingData;
70 }
71
72 return DriverResult::Success;
73}
74
75DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
76 constexpr int timeout_mili = 66;
77 constexpr int MaxTries = 15;
78 int tries = 0;
79 output.resize(MaxSubCommandResponseSize);
80
81 do {
82 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
83 MaxSubCommandResponseSize, timeout_mili);
84
85 if (result < 1) {
86 LOG_ERROR(Input, "No response from joycon");
87 }
88 if (tries++ > MaxTries) {
89 return DriverResult::Timeout;
90 }
91 } while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
92
93 if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
94 return DriverResult::WrongReply;
95 }
96
97 return DriverResult::Success;
98}
99
100DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
101 std::vector<u8>& output) {
102 std::vector<u8> local_buffer(MaxResponseSize);
103
104 local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
105 local_buffer[1] = GetCounter();
106 local_buffer[10] = static_cast<u8>(sc);
107 for (std::size_t i = 0; i < buffer.size(); ++i) {
108 local_buffer[11 + i] = buffer[i];
109 }
110
111 auto result = SendData(local_buffer);
112
113 if (result != DriverResult::Success) {
114 return result;
115 }
116
117 result = GetSubCommandResponse(sc, output);
118
119 return DriverResult::Success;
120}
121
122DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
123 std::vector<u8> output;
124 return SendSubCommand(sc, buffer, output);
125}
126
127DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
128 std::vector<u8> local_buffer(MaxResponseSize);
129
130 local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
131 local_buffer[1] = GetCounter();
132 local_buffer[10] = static_cast<u8>(sc);
133 for (std::size_t i = 0; i < buffer.size(); ++i) {
134 local_buffer[11 + i] = buffer[i];
135 }
136
137 return SendData(local_buffer);
138}
139
140DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
141 std::vector<u8> local_buffer(MaxResponseSize);
142
143 local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
144 local_buffer[1] = GetCounter();
145
146 memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
147
148 return SendData(local_buffer);
149}
150
151DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
152 constexpr std::size_t MaxTries = 10;
153 std::size_t tries = 0;
154 std::array<u8, 5> buffer = {0x00, 0x00, 0x00, 0x00, size};
155 std::vector<u8> local_buffer(size + 20);
156
157 buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
158 buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
159 do {
160 const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
161 if (result != DriverResult::Success) {
162 return result;
163 }
164
165 if (tries++ > MaxTries) {
166 return DriverResult::Timeout;
167 }
168 } while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
169
170 // Remove header from output
171 output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
172 return DriverResult::Success;
173}
174
175DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
176 const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
177 const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
178
179 if (result != DriverResult::Success) {
180 LOG_ERROR(Input, "SendMCUData failed with error {}", result);
181 }
182
183 return result;
184}
185
186DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
187 LOG_DEBUG(Input, "ConfigureMCU");
188 std::array<u8, sizeof(MCUConfig)> config_buffer;
189 memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
190 config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
191
192 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
193
194 if (result != DriverResult::Success) {
195 LOG_ERROR(Input, "Set MCU config failed with error {}", result);
196 }
197
198 return result;
199}
200
201DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
202 std::vector<u8>& output) {
203 const int report_mode = static_cast<u8>(report_mode_);
204 constexpr int TimeoutMili = 200;
205 constexpr int MaxTries = 9;
206 int tries = 0;
207 output.resize(0x170);
208
209 do {
210 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
211
212 if (result < 1) {
213 LOG_ERROR(Input, "No response from joycon attempt {}", tries);
214 }
215 if (tries++ > MaxTries) {
216 return DriverResult::Timeout;
217 }
218 } while (output[0] != report_mode || output[49] == 0xFF);
219
220 if (output[0] != report_mode || output[49] == 0xFF) {
221 return DriverResult::WrongReply;
222 }
223
224 return DriverResult::Success;
225}
226
227DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
228 std::span<const u8> buffer,
229 std::vector<u8>& output) {
230 std::vector<u8> local_buffer(MaxResponseSize);
231
232 local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
233 local_buffer[1] = GetCounter();
234 local_buffer[9] = static_cast<u8>(sc);
235 for (std::size_t i = 0; i < buffer.size(); ++i) {
236 local_buffer[10 + i] = buffer[i];
237 }
238
239 auto result = SendData(local_buffer);
240
241 if (result != DriverResult::Success) {
242 return result;
243 }
244
245 result = GetMCUDataResponse(report_mode, output);
246
247 return DriverResult::Success;
248}
249
250DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
251 std::vector<u8> output;
252 constexpr std::size_t MaxTries{8};
253 std::size_t tries{};
254
255 do {
256 const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
257 const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
258
259 if (result != DriverResult::Success) {
260 return result;
261 }
262
263 if (tries++ > MaxTries) {
264 return DriverResult::WrongReply;
265 }
266 } while (output[49] != 1 || output[56] != static_cast<u8>(mode));
267
268 return DriverResult::Success;
269}
270
271// crc-8-ccitt / polynomial 0x07 look up table
272constexpr std::array<u8, 256> mcu_crc8_table = {
273 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
274 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
275 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
276 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
277 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
278 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
279 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
280 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
281 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
282 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
283 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
284 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
285 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
286 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
287 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
288 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
289
290u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
291 u8 crc8 = 0x0;
292
293 for (int i = 0; i < size; ++i) {
294 crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
295 }
296 return crc8;
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
new file mode 100644
index 000000000..903bcf402
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -0,0 +1,173 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <memory>
12#include <span>
13#include <vector>
14
15#include "common/common_types.h"
16#include "input_common/helpers/joycon_protocol/joycon_types.h"
17
18namespace InputCommon::Joycon {
19
20/// Joycon driver functions that handle low level communication
21class JoyconCommonProtocol {
22public:
23 explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
24
25 /**
26 * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
27 * data to read before returning.
28 */
29 void SetBlocking();
30
31 /**
32 * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
33 * immediately with a value of 0 if there is no data to be read
34 */
35 void SetNonBlocking();
36
37 /**
38 * Sends a request to obtain the joycon type from device
39 * @returns controller type of the joycon
40 */
41 DriverResult GetDeviceType(ControllerType& controller_type);
42
43 /**
44 * Verifies and sets the joycon_handle if device is valid
45 * @param device info from the driver
46 * @returns success if the device is valid
47 */
48 DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
49
50 /**
51 * Sends a request to set the polling mode of the joycon
52 * @param report_mode polling mode to be set
53 */
54 DriverResult SetReportMode(Joycon::ReportMode report_mode);
55
56 /**
57 * Sends data to the joycon device
58 * @param buffer data to be send
59 */
60 DriverResult SendData(std::span<const u8> buffer);
61
62 /**
63 * Waits for incoming data of the joycon device that matchs the subcommand
64 * @param sub_command type of data to be returned
65 * @returns a buffer containing the responce
66 */
67 DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output);
68
69 /**
70 * Sends a sub command to the device and waits for it's reply
71 * @param sc sub command to be send
72 * @param buffer data to be send
73 * @returns output buffer containing the responce
74 */
75 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
76
77 /**
78 * Sends a sub command to the device and waits for it's reply and ignores the output
79 * @param sc sub command to be send
80 * @param buffer data to be send
81 */
82 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer);
83
84 /**
85 * Sends a mcu command to the device
86 * @param sc sub command to be send
87 * @param buffer data to be send
88 */
89 DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer);
90
91 /**
92 * Sends vibration data to the joycon
93 * @param buffer data to be send
94 */
95 DriverResult SendVibrationReport(std::span<const u8> buffer);
96
97 /**
98 * Reads the SPI memory stored on the joycon
99 * @param Initial address location
100 * @param size in bytes to be read
101 * @returns output buffer containing the responce
102 */
103 DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output);
104
105 /**
106 * Enables MCU chip on the joycon
107 * @param enable if true the chip will be enabled
108 */
109 DriverResult EnableMCU(bool enable);
110
111 /**
112 * Configures the MCU to the correspoinding mode
113 * @param MCUConfig configuration
114 */
115 DriverResult ConfigureMCU(const MCUConfig& config);
116
117 /**
118 * Waits until there's MCU data available. On timeout returns error
119 * @param report mode of the expected reply
120 * @returns a buffer containing the responce
121 */
122 DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output);
123
124 /**
125 * Sends data to the MCU chip and waits for it's reply
126 * @param report mode of the expected reply
127 * @param sub command to be send
128 * @param buffer data to be send
129 * @returns output buffer containing the responce
130 */
131 DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
132 std::vector<u8>& output);
133
134 /**
135 * Wait's until the MCU chip is on the specified mode
136 * @param report mode of the expected reply
137 * @param MCUMode configuration
138 */
139 DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
140
141 /**
142 * Calculates the checksum from the MCU data
143 * @param buffer containing the data to be send
144 * @param size of the buffer in bytes
145 * @returns byte with the correct checksum
146 */
147 u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
148
149private:
150 /**
151 * Increments and returns the packet counter of the handle
152 * @param joycon_handle device to send the data
153 * @returns packet counter value
154 */
155 u8 GetCounter();
156
157 std::shared_ptr<JoyconHandle> hidapi_handle;
158};
159
160class ScopedSetBlocking {
161public:
162 explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} {
163 m_self->SetBlocking();
164 }
165
166 ~ScopedSetBlocking() {
167 m_self->SetNonBlocking();
168 }
169
170private:
171 JoyconCommonProtocol* m_self{};
172};
173} // 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
new file mode 100644
index 000000000..63cfb1369
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
@@ -0,0 +1,136 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/generic_functions.h"
6
7namespace InputCommon::Joycon {
8
9GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(std::move(handle)) {}
11
12DriverResult GenericProtocol::EnablePassiveMode() {
13 ScopedSetBlocking sb(this);
14 return SetReportMode(ReportMode::SIMPLE_HID_MODE);
15}
16
17DriverResult GenericProtocol::EnableActiveMode() {
18 ScopedSetBlocking sb(this);
19 return SetReportMode(ReportMode::STANDARD_FULL_60HZ);
20}
21
22DriverResult GenericProtocol::SetLowPowerMode(bool enable) {
23 ScopedSetBlocking sb(this);
24 const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
25 return SendSubCommand(SubCommand::LOW_POWER_MODE, buffer);
26}
27
28DriverResult GenericProtocol::TriggersElapsed() {
29 ScopedSetBlocking sb(this);
30 return SendSubCommand(SubCommand::TRIGGERS_ELAPSED, {});
31}
32
33DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
34 ScopedSetBlocking sb(this);
35 std::vector<u8> output;
36
37 const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
38
39 device_info = {};
40 if (result == DriverResult::Success) {
41 memcpy(&device_info, output.data(), sizeof(DeviceInfo));
42 }
43
44 return result;
45}
46
47DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) {
48 return GetDeviceType(controller_type);
49}
50
51DriverResult GenericProtocol::EnableImu(bool enable) {
52 ScopedSetBlocking sb(this);
53 const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
54 return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
55}
56
57DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
58 AccelerometerSensitivity asen,
59 AccelerometerPerformance afrec) {
60 ScopedSetBlocking sb(this);
61 const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
62 static_cast<u8>(gfrec), static_cast<u8>(afrec)};
63 return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer);
64}
65
66DriverResult GenericProtocol::GetBattery(u32& battery_level) {
67 // This function is meant to request the high resolution battery status
68 battery_level = 0;
69 return DriverResult::NotSupported;
70}
71
72DriverResult GenericProtocol::GetColor(Color& color) {
73 ScopedSetBlocking sb(this);
74 std::vector<u8> buffer;
75 const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
76
77 color = {};
78 if (result == DriverResult::Success) {
79 color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
80 color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
81 color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]);
82 color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
83 }
84
85 return result;
86}
87
88DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
89 ScopedSetBlocking sb(this);
90 std::vector<u8> buffer;
91 const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
92
93 serial_number = {};
94 if (result == DriverResult::Success) {
95 memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber));
96 }
97
98 return result;
99}
100
101DriverResult GenericProtocol::GetTemperature(u32& temperature) {
102 // Not all devices have temperature sensor
103 temperature = 25;
104 return DriverResult::NotSupported;
105}
106
107DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
108 DeviceInfo device_info{};
109
110 const auto result = GetDeviceInfo(device_info);
111 version = device_info.firmware;
112
113 return result;
114}
115
116DriverResult GenericProtocol::SetHomeLight() {
117 ScopedSetBlocking sb(this);
118 static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
119 return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer);
120}
121
122DriverResult GenericProtocol::SetLedBusy() {
123 return DriverResult::NotSupported;
124}
125
126DriverResult GenericProtocol::SetLedPattern(u8 leds) {
127 ScopedSetBlocking sb(this);
128 const std::array<u8, 1> buffer{leds};
129 return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer);
130}
131
132DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
133 return SetLedPattern(static_cast<u8>(leds << 4));
134}
135
136} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
new file mode 100644
index 000000000..424831e81
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -0,0 +1,114 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include "input_common/helpers/joycon_protocol/common_protocol.h"
12#include "input_common/helpers/joycon_protocol/joycon_types.h"
13
14namespace InputCommon::Joycon {
15
16/// Joycon driver functions that easily implemented
17class GenericProtocol final : private JoyconCommonProtocol {
18public:
19 explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle);
20
21 /// Enables passive mode. This mode only sends button data on change. Sticks will return digital
22 /// data instead of analog. Motion will be disabled
23 DriverResult EnablePassiveMode();
24
25 /// Enables active mode. This mode will return the current status every 5-15ms
26 DriverResult EnableActiveMode();
27
28 /// Enables or disables the low power mode
29 DriverResult SetLowPowerMode(bool enable);
30
31 /// Unknown function used by the switch
32 DriverResult TriggersElapsed();
33
34 /**
35 * Sends a request to obtain the joycon firmware and mac from handle
36 * @returns controller device info
37 */
38 DriverResult GetDeviceInfo(DeviceInfo& controller_type);
39
40 /**
41 * Sends a request to obtain the joycon type from handle
42 * @returns controller type of the joycon
43 */
44 DriverResult GetControllerType(ControllerType& controller_type);
45
46 /**
47 * Enables motion input
48 * @param enable if true motion data will be enabled
49 */
50 DriverResult EnableImu(bool enable);
51
52 /**
53 * Configures the motion sensor with the specified parameters
54 * @param gsen gyroscope sensor sensitvity in degrees per second
55 * @param gfrec gyroscope sensor frequency in hertz
56 * @param asen accelerometer sensitivity in G force
57 * @param afrec accelerometer frequency in hertz
58 */
59 DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
60 AccelerometerSensitivity asen, AccelerometerPerformance afrec);
61
62 /**
63 * Request battery level from the device
64 * @returns battery level
65 */
66 DriverResult GetBattery(u32& battery_level);
67
68 /**
69 * Request joycon colors from the device
70 * @returns colors of the body and buttons
71 */
72 DriverResult GetColor(Color& color);
73
74 /**
75 * Request joycon serial number from the device
76 * @returns 16 byte serial number
77 */
78 DriverResult GetSerialNumber(SerialNumber& serial_number);
79
80 /**
81 * Request joycon serial number from the device
82 * @returns 16 byte serial number
83 */
84 DriverResult GetTemperature(u32& temperature);
85
86 /**
87 * Request joycon serial number from the device
88 * @returns 16 byte serial number
89 */
90 DriverResult GetVersionNumber(FirmwareVersion& version);
91
92 /**
93 * Sets home led behaviour
94 */
95 DriverResult SetHomeLight();
96
97 /**
98 * Sets home led into a slow breathing state
99 */
100 DriverResult SetLedBusy();
101
102 /**
103 * Sets the 4 player leds on the joycon on a solid state
104 * @params bit flag containing the led state
105 */
106 DriverResult SetLedPattern(u8 leds);
107
108 /**
109 * Sets the 4 player leds on the joycon on a blinking state
110 * @returns bit flag containing the led state
111 */
112 DriverResult SetLedBlinkPattern(u8 leds);
113};
114} // namespace InputCommon::Joycon
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..09e17bc5b
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.cpp
@@ -0,0 +1,298 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <thread>
5#include "common/logging/log.h"
6#include "input_common/helpers/joycon_protocol/irs.h"
7
8namespace InputCommon::Joycon {
9
10IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
11 : JoyconCommonProtocol(std::move(handle)) {}
12
13DriverResult IrsProtocol::EnableIrs() {
14 LOG_INFO(Input, "Enable IRS");
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17
18 if (result == DriverResult::Success) {
19 result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
20 }
21 if (result == DriverResult::Success) {
22 result = EnableMCU(true);
23 }
24 if (result == DriverResult::Success) {
25 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
26 }
27 if (result == DriverResult::Success) {
28 const MCUConfig config{
29 .command = MCUCommand::ConfigureMCU,
30 .sub_command = MCUSubCommand::SetMCUMode,
31 .mode = MCUMode::IR,
32 .crc = {},
33 };
34
35 result = ConfigureMCU(config);
36 }
37 if (result == DriverResult::Success) {
38 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
39 }
40 if (result == DriverResult::Success) {
41 result = ConfigureIrs();
42 }
43 if (result == DriverResult::Success) {
44 result = WriteRegistersStep1();
45 }
46 if (result == DriverResult::Success) {
47 result = WriteRegistersStep2();
48 }
49
50 is_enabled = true;
51
52 return result;
53}
54
55DriverResult IrsProtocol::DisableIrs() {
56 LOG_DEBUG(Input, "Disable IRS");
57 ScopedSetBlocking sb(this);
58 DriverResult result{DriverResult::Success};
59
60 if (result == DriverResult::Success) {
61 result = EnableMCU(false);
62 }
63
64 is_enabled = false;
65
66 return result;
67}
68
69DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) {
70 irs_mode = mode;
71 switch (format) {
72 case IrsResolution::Size320x240:
73 resolution_code = IrsResolutionCode::Size320x240;
74 fragments = IrsFragments::Size320x240;
75 resolution = IrsResolution::Size320x240;
76 break;
77 case IrsResolution::Size160x120:
78 resolution_code = IrsResolutionCode::Size160x120;
79 fragments = IrsFragments::Size160x120;
80 resolution = IrsResolution::Size160x120;
81 break;
82 case IrsResolution::Size80x60:
83 resolution_code = IrsResolutionCode::Size80x60;
84 fragments = IrsFragments::Size80x60;
85 resolution = IrsResolution::Size80x60;
86 break;
87 case IrsResolution::Size20x15:
88 resolution_code = IrsResolutionCode::Size20x15;
89 fragments = IrsFragments::Size20x15;
90 resolution = IrsResolution::Size20x15;
91 break;
92 case IrsResolution::Size40x30:
93 default:
94 resolution_code = IrsResolutionCode::Size40x30;
95 fragments = IrsFragments::Size40x30;
96 resolution = IrsResolution::Size40x30;
97 break;
98 }
99
100 // Restart feature
101 if (is_enabled) {
102 DisableIrs();
103 return EnableIrs();
104 }
105
106 return DriverResult::Success;
107}
108
109DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) {
110 const u8 next_packet_fragment =
111 static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1));
112
113 if (buffer[0] == 0x31 && buffer[49] == 0x03) {
114 u8 new_packet_fragment = buffer[52];
115 if (new_packet_fragment == next_packet_fragment) {
116 packet_fragment = next_packet_fragment;
117 memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300);
118
119 return RequestFrame(packet_fragment);
120 }
121
122 if (new_packet_fragment == packet_fragment) {
123 return RequestFrame(packet_fragment);
124 }
125
126 return ResendFrame(next_packet_fragment);
127 }
128
129 return RequestFrame(packet_fragment);
130}
131
132DriverResult IrsProtocol::ConfigureIrs() {
133 LOG_DEBUG(Input, "Configure IRS");
134 constexpr std::size_t max_tries = 28;
135 std::vector<u8> output;
136 std::size_t tries = 0;
137
138 const IrsConfigure irs_configuration{
139 .command = MCUCommand::ConfigureIR,
140 .sub_command = MCUSubCommand::SetDeviceMode,
141 .irs_mode = IrsMode::ImageTransfer,
142 .number_of_fragments = fragments,
143 .mcu_major_version = 0x0500,
144 .mcu_minor_version = 0x1800,
145 .crc = {},
146 };
147 buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
148
149 std::array<u8, sizeof(IrsConfigure)> request_data{};
150 memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
151 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
152 do {
153 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
154
155 if (result != DriverResult::Success) {
156 return result;
157 }
158 if (tries++ >= max_tries) {
159 return DriverResult::WrongReply;
160 }
161 } while (output[15] != 0x0b);
162
163 return DriverResult::Success;
164}
165
166DriverResult IrsProtocol::WriteRegistersStep1() {
167 LOG_DEBUG(Input, "WriteRegistersStep1");
168 DriverResult result{DriverResult::Success};
169 constexpr std::size_t max_tries = 28;
170 std::vector<u8> output;
171 std::size_t tries = 0;
172
173 const IrsWriteRegisters irs_registers{
174 .command = MCUCommand::ConfigureIR,
175 .sub_command = MCUSubCommand::WriteDeviceRegisters,
176 .number_of_registers = 0x9,
177 .registers =
178 {
179 IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)},
180 {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)},
181 {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)},
182 {IrRegistersAddress::ExposureTime, 0x00},
183 {IrRegistersAddress::Leds, static_cast<u8>(leds)},
184 {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)},
185 {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)},
186 {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)},
187 {IrRegistersAddress::WhitePixelThreshold, 0xc8},
188 },
189 .crc = {},
190 };
191
192 std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
193 memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
194 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
195
196 std::array<u8, 38> mcu_request{0x02};
197 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
198 mcu_request[37] = 0xFF;
199
200 if (result != DriverResult::Success) {
201 return result;
202 }
203
204 do {
205 result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
206
207 // First time we need to set the report mode
208 if (result == DriverResult::Success && tries == 0) {
209 result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
210 }
211 if (result == DriverResult::Success && tries == 0) {
212 GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
213 }
214
215 if (result != DriverResult::Success) {
216 return result;
217 }
218 if (tries++ >= max_tries) {
219 return DriverResult::WrongReply;
220 }
221 } while (!(output[15] == 0x13 && output[17] == 0x07) && output[15] != 0x23);
222
223 return DriverResult::Success;
224}
225
226DriverResult IrsProtocol::WriteRegistersStep2() {
227 LOG_DEBUG(Input, "WriteRegistersStep2");
228 constexpr std::size_t max_tries = 28;
229 std::vector<u8> output;
230 std::size_t tries = 0;
231
232 const IrsWriteRegisters irs_registers{
233 .command = MCUCommand::ConfigureIR,
234 .sub_command = MCUSubCommand::WriteDeviceRegisters,
235 .number_of_registers = 0x8,
236 .registers =
237 {
238 IrsRegister{IrRegistersAddress::LedIntensitiyMSB,
239 static_cast<u8>(led_intensity >> 8)},
240 {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)},
241 {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)},
242 {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)},
243 {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)},
244 {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)},
245 {IrRegistersAddress::UpdateTime, 0x2d},
246 {IrRegistersAddress::FinalizeConfig, 0x01},
247 },
248 .crc = {},
249 };
250
251 std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
252 memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
253 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
254 do {
255 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
256
257 if (result != DriverResult::Success) {
258 return result;
259 }
260 if (tries++ >= max_tries) {
261 return DriverResult::WrongReply;
262 }
263 } while (output[15] != 0x13 && output[15] != 0x23);
264
265 return DriverResult::Success;
266}
267
268DriverResult IrsProtocol::RequestFrame(u8 frame) {
269 std::array<u8, 38> mcu_request{};
270 mcu_request[3] = frame;
271 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
272 mcu_request[37] = 0xFF;
273 return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
274}
275
276DriverResult IrsProtocol::ResendFrame(u8 frame) {
277 std::array<u8, 38> mcu_request{};
278 mcu_request[1] = 0x1;
279 mcu_request[2] = frame;
280 mcu_request[3] = 0x0;
281 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
282 mcu_request[37] = 0xFF;
283 return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
284}
285
286std::vector<u8> IrsProtocol::GetImage() const {
287 return buf_image;
288}
289
290IrsResolution IrsProtocol::GetIrsFormat() const {
291 return resolution;
292}
293
294bool IrsProtocol::IsEnabled() const {
295 return is_enabled;
296}
297
298} // 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 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class IrsProtocol final : private JoyconCommonProtocol {
19public:
20 explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableIrs();
23
24 DriverResult DisableIrs();
25
26 DriverResult SetIrsConfig(IrsMode mode, IrsResolution format);
27
28 DriverResult RequestImage(std::span<u8> buffer);
29
30 std::vector<u8> GetImage() const;
31
32 IrsResolution GetIrsFormat() const;
33
34 bool IsEnabled() const;
35
36private:
37 DriverResult ConfigureIrs();
38
39 DriverResult WriteRegistersStep1();
40 DriverResult WriteRegistersStep2();
41
42 DriverResult RequestFrame(u8 frame);
43 DriverResult ResendFrame(u8 frame);
44
45 IrsMode irs_mode{IrsMode::ImageTransfer};
46 IrsResolution resolution{IrsResolution::Size40x30};
47 IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30};
48 IrsFragments fragments{IrsFragments::Size40x30};
49 IrLeds leds{IrLeds::BrightAndDim};
50 IrExLedFilter led_filter{IrExLedFilter::Enabled};
51 IrImageFlip image_flip{IrImageFlip::Normal};
52 u8 digital_gain{0x01};
53 u16 exposure{0x2490};
54 u16 led_intensity{0x0f10};
55 u32 denoise{0x012344};
56
57 u8 packet_fragment{};
58 std::vector<u8> buf_image; // 8bpp greyscale image.
59
60 bool is_enabled{};
61};
62
63} // 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
new file mode 100644
index 000000000..182d2c15b
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -0,0 +1,613 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <array>
12#include <functional>
13#include <SDL_hidapi.h>
14
15#include "common/bit_field.h"
16#include "common/common_funcs.h"
17#include "common/common_types.h"
18
19namespace InputCommon::Joycon {
20constexpr u32 MaxErrorCount = 50;
21constexpr u32 MaxBufferSize = 368;
22constexpr u32 MaxResponseSize = 49;
23constexpr u32 MaxSubCommandResponseSize = 64;
24constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
25
26using MacAddress = std::array<u8, 6>;
27using SerialNumber = std::array<u8, 15>;
28
29enum class ControllerType {
30 None,
31 Left,
32 Right,
33 Pro,
34 Grip,
35 Dual,
36};
37
38enum class PadAxes {
39 LeftStickX,
40 LeftStickY,
41 RightStickX,
42 RightStickY,
43 Undefined,
44};
45
46enum class PadMotion {
47 LeftMotion,
48 RightMotion,
49 Undefined,
50};
51
52enum class PadButton : u32 {
53 Down = 0x000001,
54 Up = 0x000002,
55 Right = 0x000004,
56 Left = 0x000008,
57 LeftSR = 0x000010,
58 LeftSL = 0x000020,
59 L = 0x000040,
60 ZL = 0x000080,
61 Y = 0x000100,
62 X = 0x000200,
63 B = 0x000400,
64 A = 0x000800,
65 RightSR = 0x001000,
66 RightSL = 0x002000,
67 R = 0x004000,
68 ZR = 0x008000,
69 Minus = 0x010000,
70 Plus = 0x020000,
71 StickR = 0x040000,
72 StickL = 0x080000,
73 Home = 0x100000,
74 Capture = 0x200000,
75};
76
77enum class PasivePadButton : u32 {
78 Down_A = 0x0001,
79 Right_X = 0x0002,
80 Left_B = 0x0004,
81 Up_Y = 0x0008,
82 SL = 0x0010,
83 SR = 0x0020,
84 Minus = 0x0100,
85 Plus = 0x0200,
86 StickL = 0x0400,
87 StickR = 0x0800,
88 Home = 0x1000,
89 Capture = 0x2000,
90 L_R = 0x4000,
91 ZL_ZR = 0x8000,
92};
93
94enum class OutputReport : u8 {
95 RUMBLE_AND_SUBCMD = 0x01,
96 FW_UPDATE_PKT = 0x03,
97 RUMBLE_ONLY = 0x10,
98 MCU_DATA = 0x11,
99 USB_CMD = 0x80,
100};
101
102enum class InputReport : u8 {
103 SUBCMD_REPLY = 0x21,
104 STANDARD_FULL_60HZ = 0x30,
105 NFC_IR_MODE_60HZ = 0x31,
106 SIMPLE_HID_MODE = 0x3F,
107 INPUT_USB_RESPONSE = 0x81,
108};
109
110enum class FeatureReport : u8 {
111 Last_SUBCMD = 0x02,
112 OTA_GW_UPGRADE = 0x70,
113 SETUP_MEM_READ = 0x71,
114 MEM_READ = 0x72,
115 ERASE_MEM_SECTOR = 0x73,
116 MEM_WRITE = 0x74,
117 LAUNCH = 0x75,
118};
119
120enum class SubCommand : u8 {
121 STATE = 0x00,
122 MANUAL_BT_PAIRING = 0x01,
123 REQ_DEV_INFO = 0x02,
124 SET_REPORT_MODE = 0x03,
125 TRIGGERS_ELAPSED = 0x04,
126 GET_PAGE_LIST_STATE = 0x05,
127 SET_HCI_STATE = 0x06,
128 RESET_PAIRING_INFO = 0x07,
129 LOW_POWER_MODE = 0x08,
130 SPI_FLASH_READ = 0x10,
131 SPI_FLASH_WRITE = 0x11,
132 SPI_SECTOR_ERASE = 0x12,
133 RESET_MCU = 0x20,
134 SET_MCU_CONFIG = 0x21,
135 SET_MCU_STATE = 0x22,
136 SET_PLAYER_LIGHTS = 0x30,
137 GET_PLAYER_LIGHTS = 0x31,
138 SET_HOME_LIGHT = 0x38,
139 ENABLE_IMU = 0x40,
140 SET_IMU_SENSITIVITY = 0x41,
141 WRITE_IMU_REG = 0x42,
142 READ_IMU_REG = 0x43,
143 ENABLE_VIBRATION = 0x48,
144 GET_REGULATED_VOLTAGE = 0x50,
145 SET_EXTERNAL_CONFIG = 0x58,
146 UNKNOWN_RINGCON = 0x59,
147 UNKNOWN_RINGCON2 = 0x5A,
148 UNKNOWN_RINGCON3 = 0x5C,
149};
150
151enum class UsbSubCommand : u8 {
152 CONN_STATUS = 0x01,
153 HADSHAKE = 0x02,
154 BAUDRATE_3M = 0x03,
155 NO_TIMEOUT = 0x04,
156 EN_TIMEOUT = 0x05,
157 RESET = 0x06,
158 PRE_HANDSHAKE = 0x91,
159 SEND_UART = 0x92,
160};
161
162enum class CalMagic : u8 {
163 USR_MAGIC_0 = 0xB2,
164 USR_MAGIC_1 = 0xA1,
165 USRR_MAGI_SIZE = 2,
166};
167
168enum class CalAddr {
169 SERIAL_NUMBER = 0X6000,
170 DEVICE_TYPE = 0X6012,
171 COLOR_EXIST = 0X601B,
172 FACT_LEFT_DATA = 0X603d,
173 FACT_RIGHT_DATA = 0X6046,
174 COLOR_DATA = 0X6050,
175 FACT_IMU_DATA = 0X6020,
176 USER_LEFT_MAGIC = 0X8010,
177 USER_LEFT_DATA = 0X8012,
178 USER_RIGHT_MAGIC = 0X801B,
179 USER_RIGHT_DATA = 0X801D,
180 USER_IMU_MAGIC = 0X8026,
181 USER_IMU_DATA = 0X8028,
182};
183
184enum class ReportMode : u8 {
185 ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
186 ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
187 ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
188 ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
189 MCU_UPDATE_STATE = 0x23,
190 STANDARD_FULL_60HZ = 0x30,
191 NFC_IR_MODE_60HZ = 0x31,
192 SIMPLE_HID_MODE = 0x3F,
193};
194
195enum class GyroSensitivity : u8 {
196 DPS250,
197 DPS500,
198 DPS1000,
199 DPS2000, // Default
200};
201
202enum class AccelerometerSensitivity : u8 {
203 G8, // Default
204 G4,
205 G2,
206 G16,
207};
208
209enum class GyroPerformance : u8 {
210 HZ833,
211 HZ208, // Default
212};
213
214enum class AccelerometerPerformance : u8 {
215 HZ200,
216 HZ100, // Default
217};
218
219enum class MCUCommand : u8 {
220 ConfigureMCU = 0x21,
221 ConfigureIR = 0x23,
222};
223
224enum class MCUSubCommand : u8 {
225 SetMCUMode = 0x0,
226 SetDeviceMode = 0x1,
227 ReadDeviceMode = 0x02,
228 WriteDeviceRegisters = 0x4,
229};
230
231enum class MCUMode : u8 {
232 Suspend = 0,
233 Standby = 1,
234 Ringcon = 3,
235 NFC = 4,
236 IR = 5,
237 MaybeFWUpdate = 6,
238};
239
240enum class MCURequest : u8 {
241 GetMCUStatus = 1,
242 GetNFCData = 2,
243 GetIRData = 3,
244};
245
246enum class MCUReport : u8 {
247 Empty = 0x00,
248 StateReport = 0x01,
249 IRData = 0x03,
250 BusyInitializing = 0x0b,
251 IRStatus = 0x13,
252 IRRegisters = 0x1b,
253 NFCState = 0x2a,
254 NFCReadData = 0x3a,
255 EmptyAwaitingCmd = 0xff,
256};
257
258enum class MCUPacketFlag : u8 {
259 MorePacketsRemaining = 0x00,
260 LastCommandPacket = 0x08,
261};
262
263enum class NFCReadCommand : u8 {
264 CancelAll = 0x00,
265 StartPolling = 0x01,
266 StopPolling = 0x02,
267 StartWaitingRecieve = 0x04,
268 Ntag = 0x06,
269 Mifare = 0x0F,
270};
271
272enum class NFCTagType : u8 {
273 AllTags = 0x00,
274 Ntag215 = 0x01,
275};
276
277enum class NFCPages {
278 Block0 = 0,
279 Block45 = 45,
280 Block135 = 135,
281 Block231 = 231,
282};
283
284enum class NFCStatus : u8 {
285 LastPackage = 0x04,
286 TagLost = 0x07,
287};
288
289enum class IrsMode : u8 {
290 None = 0x02,
291 Moment = 0x03,
292 Dpd = 0x04,
293 Clustering = 0x06,
294 ImageTransfer = 0x07,
295 Silhouette = 0x08,
296 TeraImage = 0x09,
297 SilhouetteTeraImage = 0x0A,
298};
299
300enum class IrsResolution {
301 Size320x240,
302 Size160x120,
303 Size80x60,
304 Size40x30,
305 Size20x15,
306 None,
307};
308
309enum class IrsResolutionCode : u8 {
310 Size320x240 = 0x00, // Full pixel array
311 Size160x120 = 0x50, // Sensor Binning [2 X 2]
312 Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2]
313 Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4]
314 Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4]
315};
316
317// Size of image divided by 300
318enum class IrsFragments : u8 {
319 Size20x15 = 0x00,
320 Size40x30 = 0x03,
321 Size80x60 = 0x0f,
322 Size160x120 = 0x3f,
323 Size320x240 = 0xFF,
324};
325
326enum class IrLeds : u8 {
327 BrightAndDim = 0x00,
328 Bright = 0x20,
329 Dim = 0x10,
330 None = 0x30,
331};
332
333enum class IrExLedFilter : u8 {
334 Disabled = 0x00,
335 Enabled = 0x03,
336};
337
338enum class IrImageFlip : u8 {
339 Normal = 0x00,
340 Inverted = 0x02,
341};
342
343enum class IrRegistersAddress : u16 {
344 UpdateTime = 0x0400,
345 FinalizeConfig = 0x0700,
346 LedFilter = 0x0e00,
347 Leds = 0x1000,
348 LedIntensitiyMSB = 0x1100,
349 LedIntensitiyLSB = 0x1200,
350 ImageFlip = 0x2d00,
351 Resolution = 0x2e00,
352 DigitalGainLSB = 0x2e01,
353 DigitalGainMSB = 0x2f01,
354 ExposureLSB = 0x3001,
355 ExposureMSB = 0x3101,
356 ExposureTime = 0x3201,
357 WhitePixelThreshold = 0x4301,
358 DenoiseSmoothing = 0x6701,
359 DenoiseEdge = 0x6801,
360 DenoiseColor = 0x6901,
361};
362
363enum class DriverResult {
364 Success,
365 WrongReply,
366 Timeout,
367 UnsupportedControllerType,
368 HandleInUse,
369 ErrorReadingData,
370 ErrorWritingData,
371 NoDeviceDetected,
372 InvalidHandle,
373 NotSupported,
374 Disabled,
375 Unknown,
376};
377
378struct MotionSensorCalibration {
379 s16 offset;
380 s16 scale;
381};
382
383struct MotionCalibration {
384 std::array<MotionSensorCalibration, 3> accelerometer;
385 std::array<MotionSensorCalibration, 3> gyro;
386};
387
388// Basic motion data containing data from the sensors and a timestamp in microseconds
389struct MotionData {
390 float gyro_x{};
391 float gyro_y{};
392 float gyro_z{};
393 float accel_x{};
394 float accel_y{};
395 float accel_z{};
396 u64 delta_timestamp{};
397};
398
399struct JoyStickAxisCalibration {
400 u16 max{1};
401 u16 min{1};
402 u16 center{0};
403};
404
405struct JoyStickCalibration {
406 JoyStickAxisCalibration x;
407 JoyStickAxisCalibration y;
408};
409
410struct RingCalibration {
411 s16 default_value;
412 s16 max_value;
413 s16 min_value;
414};
415
416struct Color {
417 u32 body;
418 u32 buttons;
419 u32 left_grip;
420 u32 right_grip;
421};
422
423struct Battery {
424 union {
425 u8 raw{};
426
427 BitField<0, 4, u8> unknown;
428 BitField<4, 1, u8> charging;
429 BitField<5, 3, u8> status;
430 };
431};
432
433struct VibrationValue {
434 f32 low_amplitude;
435 f32 low_frequency;
436 f32 high_amplitude;
437 f32 high_frequency;
438};
439
440struct JoyconHandle {
441 SDL_hid_device* handle = nullptr;
442 u8 packet_counter{};
443};
444
445struct MCUConfig {
446 MCUCommand command;
447 MCUSubCommand sub_command;
448 MCUMode mode;
449 INSERT_PADDING_BYTES(0x22);
450 u8 crc;
451};
452static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
453
454#pragma pack(push, 1)
455struct InputReportPassive {
456 InputReport report_mode;
457 u16 button_input;
458 u8 stick_state;
459 std::array<u8, 10> unknown_data;
460};
461static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
462
463struct InputReportActive {
464 InputReport report_mode;
465 u8 packet_id;
466 Battery battery_status;
467 std::array<u8, 3> button_input;
468 std::array<u8, 3> left_stick_state;
469 std::array<u8, 3> right_stick_state;
470 u8 vibration_code;
471 std::array<s16, 6 * 2> motion_input;
472 INSERT_PADDING_BYTES(0x2);
473 s16 ring_input;
474};
475static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
476
477struct InputReportNfcIr {
478 InputReport report_mode;
479 u8 packet_id;
480 Battery battery_status;
481 std::array<u8, 3> button_input;
482 std::array<u8, 3> left_stick_state;
483 std::array<u8, 3> right_stick_state;
484 u8 vibration_code;
485 std::array<s16, 6 * 2> motion_input;
486 INSERT_PADDING_BYTES(0x4);
487};
488static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
489#pragma pack(pop)
490
491struct IMUCalibration {
492 std::array<s16, 3> accelerometer_offset;
493 std::array<s16, 3> accelerometer_scale;
494 std::array<s16, 3> gyroscope_offset;
495 std::array<s16, 3> gyroscope_scale;
496};
497static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
498
499struct NFCReadBlock {
500 u8 start;
501 u8 end;
502};
503static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
504
505struct NFCReadBlockCommand {
506 u8 block_count{};
507 std::array<NFCReadBlock, 4> blocks{};
508};
509static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
510
511struct NFCReadCommandData {
512 u8 unknown;
513 u8 uuid_length;
514 u8 unknown_2;
515 std::array<u8, 6> uid;
516 NFCTagType tag_type;
517 NFCReadBlockCommand read_block;
518};
519static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
520
521struct NFCPollingCommandData {
522 u8 enable_mifare;
523 u8 unknown_1;
524 u8 unknown_2;
525 u8 unknown_3;
526 u8 unknown_4;
527};
528static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
529
530struct NFCRequestState {
531 MCUSubCommand sub_command;
532 NFCReadCommand command_argument;
533 u8 packet_id;
534 INSERT_PADDING_BYTES(0x1);
535 MCUPacketFlag packet_flag;
536 u8 data_length;
537 union {
538 std::array<u8, 0x1F> raw_data;
539 NFCReadCommandData nfc_read;
540 NFCPollingCommandData nfc_polling;
541 };
542 u8 crc;
543};
544static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
545
546struct IrsConfigure {
547 MCUCommand command;
548 MCUSubCommand sub_command;
549 IrsMode irs_mode;
550 IrsFragments number_of_fragments;
551 u16 mcu_major_version;
552 u16 mcu_minor_version;
553 INSERT_PADDING_BYTES(0x1D);
554 u8 crc;
555};
556static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
557
558#pragma pack(push, 1)
559struct IrsRegister {
560 IrRegistersAddress address;
561 u8 value;
562};
563static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
564
565struct IrsWriteRegisters {
566 MCUCommand command;
567 MCUSubCommand sub_command;
568 u8 number_of_registers;
569 std::array<IrsRegister, 9> registers;
570 INSERT_PADDING_BYTES(0x7);
571 u8 crc;
572};
573static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
574#pragma pack(pop)
575
576struct FirmwareVersion {
577 u8 major;
578 u8 minor;
579};
580static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
581
582struct DeviceInfo {
583 FirmwareVersion firmware;
584 MacAddress mac_address;
585};
586static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
587
588struct MotionStatus {
589 bool is_enabled;
590 u64 delta_time;
591 GyroSensitivity gyro_sensitivity;
592 AccelerometerSensitivity accelerometer_sensitivity;
593};
594
595struct RingStatus {
596 bool is_enabled;
597 s16 default_value;
598 s16 max_value;
599 s16 min_value;
600};
601
602struct JoyconCallbacks {
603 std::function<void(Battery)> on_battery_data;
604 std::function<void(Color)> on_color_data;
605 std::function<void(int, bool)> on_button_data;
606 std::function<void(int, f32)> on_stick_data;
607 std::function<void(int, const MotionData&)> on_motion_data;
608 std::function<void(f32)> on_ring_data;
609 std::function<void(const std::vector<u8>&)> on_amiibo_data;
610 std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
611};
612
613} // namespace InputCommon::Joycon
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 000000000..5c0f71722
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -0,0 +1,400 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <thread>
5#include "common/logging/log.h"
6#include "input_common/helpers/joycon_protocol/nfc.h"
7
8namespace InputCommon::Joycon {
9
10NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
11 : JoyconCommonProtocol(std::move(handle)) {}
12
13DriverResult NfcProtocol::EnableNfc() {
14 LOG_INFO(Input, "Enable NFC");
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17
18 if (result == DriverResult::Success) {
19 result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
20 }
21 if (result == DriverResult::Success) {
22 result = EnableMCU(true);
23 }
24 if (result == DriverResult::Success) {
25 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
26 }
27 if (result == DriverResult::Success) {
28 const MCUConfig config{
29 .command = MCUCommand::ConfigureMCU,
30 .sub_command = MCUSubCommand::SetMCUMode,
31 .mode = MCUMode::NFC,
32 .crc = {},
33 };
34
35 result = ConfigureMCU(config);
36 }
37
38 return result;
39}
40
41DriverResult NfcProtocol::DisableNfc() {
42 LOG_DEBUG(Input, "Disable NFC");
43 ScopedSetBlocking sb(this);
44 DriverResult result{DriverResult::Success};
45
46 if (result == DriverResult::Success) {
47 result = EnableMCU(false);
48 }
49
50 is_enabled = false;
51
52 return result;
53}
54
55DriverResult NfcProtocol::StartNFCPollingMode() {
56 LOG_DEBUG(Input, "Start NFC pooling Mode");
57 ScopedSetBlocking sb(this);
58 DriverResult result{DriverResult::Success};
59 TagFoundData tag_data{};
60
61 if (result == DriverResult::Success) {
62 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
63 }
64 if (result == DriverResult::Success) {
65 result = WaitUntilNfcIsReady();
66 }
67 if (result == DriverResult::Success) {
68 is_enabled = true;
69 }
70
71 return result;
72}
73
74DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
75 LOG_DEBUG(Input, "Start NFC pooling Mode");
76 ScopedSetBlocking sb(this);
77 DriverResult result{DriverResult::Success};
78 TagFoundData tag_data{};
79
80 if (result == DriverResult::Success) {
81 result = StartPolling(tag_data);
82 }
83 if (result == DriverResult::Success) {
84 result = ReadTag(tag_data);
85 }
86 if (result == DriverResult::Success) {
87 result = WaitUntilNfcIsReady();
88 }
89 if (result == DriverResult::Success) {
90 result = StartPolling(tag_data);
91 }
92 if (result == DriverResult::Success) {
93 result = GetAmiiboData(data);
94 }
95
96 return result;
97}
98
99bool NfcProtocol::HasAmiibo() {
100 ScopedSetBlocking sb(this);
101 DriverResult result{DriverResult::Success};
102 TagFoundData tag_data{};
103
104 if (result == DriverResult::Success) {
105 result = StartPolling(tag_data);
106 }
107
108 return result == DriverResult::Success;
109}
110
111DriverResult NfcProtocol::WaitUntilNfcIsReady() {
112 constexpr std::size_t timeout_limit = 10;
113 std::vector<u8> output;
114 std::size_t tries = 0;
115
116 do {
117 auto result = SendStartWaitingRecieveRequest(output);
118
119 if (result != DriverResult::Success) {
120 return result;
121 }
122 if (tries++ > timeout_limit) {
123 return DriverResult::Timeout;
124 }
125 } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 ||
126 output[56] != 0x00);
127
128 return DriverResult::Success;
129}
130
131DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
132 LOG_DEBUG(Input, "Start Polling for tag");
133 constexpr std::size_t timeout_limit = 7;
134 std::vector<u8> output;
135 std::size_t tries = 0;
136
137 do {
138 const auto result = SendStartPollingRequest(output);
139 if (result != DriverResult::Success) {
140 return result;
141 }
142 if (tries++ > timeout_limit) {
143 return DriverResult::Timeout;
144 }
145 } while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09);
146
147 data.type = output[62];
148 data.uuid.resize(output[64]);
149 memcpy(data.uuid.data(), output.data() + 65, data.uuid.size());
150
151 return DriverResult::Success;
152}
153
154DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
155 constexpr std::size_t timeout_limit = 10;
156 std::vector<u8> output;
157 std::size_t tries = 0;
158
159 std::string uuid_string;
160 for (auto& content : data.uuid) {
161 uuid_string += fmt::format(" {:02x}", content);
162 }
163
164 LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
165
166 tries = 0;
167 NFCPages ntag_pages = NFCPages::Block0;
168 // Read Tag data
169 while (true) {
170 auto result = SendReadAmiiboRequest(output, ntag_pages);
171 const auto mcu_report = static_cast<MCUReport>(output[49]);
172 const auto nfc_status = static_cast<NFCStatus>(output[56]);
173
174 if (result != DriverResult::Success) {
175 return result;
176 }
177
178 if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
179 nfc_status == NFCStatus::TagLost) {
180 return DriverResult::ErrorReadingData;
181 }
182
183 if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07 && output[52] == 0x01) {
184 if (data.type != 2) {
185 continue;
186 }
187 switch (output[74]) {
188 case 0:
189 ntag_pages = NFCPages::Block135;
190 break;
191 case 3:
192 ntag_pages = NFCPages::Block45;
193 break;
194 case 4:
195 ntag_pages = NFCPages::Block231;
196 break;
197 default:
198 return DriverResult::ErrorReadingData;
199 }
200 continue;
201 }
202
203 if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
204 // finished
205 SendStopPollingRequest(output);
206 return DriverResult::Success;
207 }
208
209 // Ignore other state reports
210 if (mcu_report == MCUReport::NFCState) {
211 continue;
212 }
213
214 if (tries++ > timeout_limit) {
215 return DriverResult::Timeout;
216 }
217 }
218
219 return DriverResult::Success;
220}
221
222DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
223 constexpr std::size_t timeout_limit = 10;
224 std::vector<u8> output;
225 std::size_t tries = 0;
226
227 NFCPages ntag_pages = NFCPages::Block135;
228 std::size_t ntag_buffer_pos = 0;
229 // Read Tag data
230 while (true) {
231 auto result = SendReadAmiiboRequest(output, ntag_pages);
232 const auto mcu_report = static_cast<MCUReport>(output[49]);
233 const auto nfc_status = static_cast<NFCStatus>(output[56]);
234
235 if (result != DriverResult::Success) {
236 return result;
237 }
238
239 if ((mcu_report == MCUReport::NFCReadData || mcu_report == MCUReport::NFCState) &&
240 nfc_status == NFCStatus::TagLost) {
241 return DriverResult::ErrorReadingData;
242 }
243
244 if (mcu_report == MCUReport::NFCReadData && output[51] == 0x07) {
245 std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
246 if (output[52] == 0x01) {
247 memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116, payload_size - 60);
248 ntag_buffer_pos += payload_size - 60;
249 } else {
250 memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
251 }
252 continue;
253 }
254
255 if (mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
256 LOG_INFO(Input, "Finished reading amiibo");
257 return DriverResult::Success;
258 }
259
260 // Ignore other state reports
261 if (mcu_report == MCUReport::NFCState) {
262 continue;
263 }
264
265 if (tries++ > timeout_limit) {
266 return DriverResult::Timeout;
267 }
268 }
269
270 return DriverResult::Success;
271}
272
273DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
274 NFCRequestState request{
275 .sub_command = MCUSubCommand::ReadDeviceMode,
276 .command_argument = NFCReadCommand::StartPolling,
277 .packet_id = 0x0,
278 .packet_flag = MCUPacketFlag::LastCommandPacket,
279 .data_length = sizeof(NFCPollingCommandData),
280 .nfc_polling =
281 {
282 .enable_mifare = 0x01,
283 .unknown_1 = 0x00,
284 .unknown_2 = 0x00,
285 .unknown_3 = 0x2c,
286 .unknown_4 = 0x01,
287 },
288 .crc = {},
289 };
290
291 std::array<u8, sizeof(NFCRequestState)> request_data{};
292 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
293 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
294 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
295}
296
297DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
298 NFCRequestState request{
299 .sub_command = MCUSubCommand::ReadDeviceMode,
300 .command_argument = NFCReadCommand::StopPolling,
301 .packet_id = 0x0,
302 .packet_flag = MCUPacketFlag::LastCommandPacket,
303 .data_length = 0,
304 .raw_data = {},
305 .crc = {},
306 };
307
308 std::array<u8, sizeof(NFCRequestState)> request_data{};
309 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
310 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
311 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
312}
313
314DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) {
315 NFCRequestState request{
316 .sub_command = MCUSubCommand::ReadDeviceMode,
317 .command_argument = NFCReadCommand::StartWaitingRecieve,
318 .packet_id = 0x0,
319 .packet_flag = MCUPacketFlag::LastCommandPacket,
320 .data_length = 0,
321 .raw_data = {},
322 .crc = {},
323 };
324
325 std::vector<u8> request_data(sizeof(NFCRequestState));
326 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
327 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
328 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
329}
330
331DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages) {
332 NFCRequestState request{
333 .sub_command = MCUSubCommand::ReadDeviceMode,
334 .command_argument = NFCReadCommand::Ntag,
335 .packet_id = 0x0,
336 .packet_flag = MCUPacketFlag::LastCommandPacket,
337 .data_length = sizeof(NFCReadCommandData),
338 .nfc_read =
339 {
340 .unknown = 0xd0,
341 .uuid_length = 0x07,
342 .unknown_2 = 0x00,
343 .uid = {},
344 .tag_type = NFCTagType::AllTags,
345 .read_block = GetReadBlockCommand(ntag_pages),
346 },
347 .crc = {},
348 };
349
350 std::array<u8, sizeof(NFCRequestState)> request_data{};
351 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
352 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
353 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
354}
355
356NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
357 switch (pages) {
358 case NFCPages::Block0:
359 return {
360 .block_count = 1,
361 };
362 case NFCPages::Block45:
363 return {
364 .block_count = 1,
365 .blocks =
366 {
367 NFCReadBlock{0x00, 0x2C},
368 },
369 };
370 case NFCPages::Block135:
371 return {
372 .block_count = 3,
373 .blocks =
374 {
375 NFCReadBlock{0x00, 0x3b},
376 {0x3c, 0x77},
377 {0x78, 0x86},
378 },
379 };
380 case NFCPages::Block231:
381 return {
382 .block_count = 4,
383 .blocks =
384 {
385 NFCReadBlock{0x00, 0x3b},
386 {0x3c, 0x77},
387 {0x78, 0x83},
388 {0xb4, 0xe6},
389 },
390 };
391 default:
392 return {};
393 };
394}
395
396bool NfcProtocol::IsEnabled() const {
397 return is_enabled;
398}
399
400} // 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 000000000..e63665aa9
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class NfcProtocol final : private JoyconCommonProtocol {
19public:
20 explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableNfc();
23
24 DriverResult DisableNfc();
25
26 DriverResult StartNFCPollingMode();
27
28 DriverResult ScanAmiibo(std::vector<u8>& data);
29
30 bool HasAmiibo();
31
32 bool IsEnabled() const;
33
34private:
35 struct TagFoundData {
36 u8 type;
37 std::vector<u8> uuid;
38 };
39
40 DriverResult WaitUntilNfcIsReady();
41
42 DriverResult StartPolling(TagFoundData& data);
43
44 DriverResult ReadTag(const TagFoundData& data);
45
46 DriverResult GetAmiiboData(std::vector<u8>& data);
47
48 DriverResult SendStartPollingRequest(std::vector<u8>& output);
49
50 DriverResult SendStopPollingRequest(std::vector<u8>& output);
51
52 DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
53
54 DriverResult SendReadAmiiboRequest(std::vector<u8>& output, NFCPages ntag_pages);
55
56 NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
57
58 bool is_enabled{};
59};
60
61} // namespace InputCommon::Joycon
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 000000000..7f8e093fa
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -0,0 +1,341 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/poller.h"
6
7namespace InputCommon::Joycon {
8
9JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
10 JoyStickCalibration right_stick_calibration_,
11 MotionCalibration motion_calibration_)
12 : device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
13 right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
14
15void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
16 callbacks = std::move(callbacks_);
17}
18
19void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
20 const RingStatus& ring_status) {
21 InputReportActive data{};
22 memcpy(&data, buffer.data(), sizeof(InputReportActive));
23
24 switch (device_type) {
25 case Joycon::ControllerType::Left:
26 UpdateActiveLeftPadInput(data, motion_status);
27 break;
28 case Joycon::ControllerType::Right:
29 UpdateActiveRightPadInput(data, motion_status);
30 break;
31 case Joycon::ControllerType::Pro:
32 UpdateActiveProPadInput(data, motion_status);
33 break;
34 case Joycon::ControllerType::Grip:
35 case Joycon::ControllerType::Dual:
36 case Joycon::ControllerType::None:
37 break;
38 }
39
40 if (ring_status.is_enabled) {
41 UpdateRing(data.ring_input, ring_status);
42 }
43
44 callbacks.on_battery_data(data.battery_status);
45}
46
47void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
48 InputReportPassive data{};
49 memcpy(&data, buffer.data(), sizeof(InputReportPassive));
50
51 switch (device_type) {
52 case Joycon::ControllerType::Left:
53 UpdatePasiveLeftPadInput(data);
54 break;
55 case Joycon::ControllerType::Right:
56 UpdatePasiveRightPadInput(data);
57 break;
58 case Joycon::ControllerType::Pro:
59 UpdatePasiveProPadInput(data);
60 break;
61 case Joycon::ControllerType::Grip:
62 case Joycon::ControllerType::Dual:
63 case Joycon::ControllerType::None:
64 break;
65 }
66}
67
68void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
69 // This mode is compatible with the active mode
70 ReadActiveMode(buffer, motion_status, {});
71}
72
73void JoyconPoller::UpdateColor(const Color& color) {
74 callbacks.on_color_data(color);
75}
76
77void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) {
78 callbacks.on_amiibo_data(amiibo_data);
79}
80
81void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
82 callbacks.on_camera_data(camera_data, format);
83}
84
85void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
86 float normalized_value = static_cast<float>(value - ring_status.default_value);
87 if (normalized_value > 0) {
88 normalized_value = normalized_value /
89 static_cast<float>(ring_status.max_value - ring_status.default_value);
90 }
91 if (normalized_value < 0) {
92 normalized_value = normalized_value /
93 static_cast<float>(ring_status.default_value - ring_status.min_value);
94 }
95 callbacks.on_ring_data(normalized_value);
96}
97
98void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
99 const MotionStatus& motion_status) {
100 static constexpr std::array<Joycon::PadButton, 11> left_buttons{
101 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
102 Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
103 Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
104 Joycon::PadButton::Capture, Joycon::PadButton::StickL,
105 };
106
107 const u32 raw_button =
108 static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
109 for (std::size_t i = 0; i < left_buttons.size(); ++i) {
110 const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
111 const int button = static_cast<int>(left_buttons[i]);
112 callbacks.on_button_data(button, button_status);
113 }
114
115 const u16 raw_left_axis_x =
116 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
117 const u16 raw_left_axis_y =
118 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
119 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
120 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
121 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
122 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
123
124 if (motion_status.is_enabled) {
125 auto left_motion = GetMotionInput(input, motion_status);
126 // Rotate motion axis to the correct direction
127 left_motion.accel_y = -left_motion.accel_y;
128 left_motion.accel_z = -left_motion.accel_z;
129 left_motion.gyro_x = -left_motion.gyro_x;
130 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
131 }
132}
133
134void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
135 const MotionStatus& motion_status) {
136 static constexpr std::array<Joycon::PadButton, 11> right_buttons{
137 Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
138 Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
139 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
140 Joycon::PadButton::Home, Joycon::PadButton::StickR,
141 };
142
143 const u32 raw_button =
144 static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
145 for (std::size_t i = 0; i < right_buttons.size(); ++i) {
146 const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
147 const int button = static_cast<int>(right_buttons[i]);
148 callbacks.on_button_data(button, button_status);
149 }
150
151 const u16 raw_right_axis_x =
152 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
153 const u16 raw_right_axis_y =
154 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
155 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
156 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
157 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
158 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
159
160 if (motion_status.is_enabled) {
161 auto right_motion = GetMotionInput(input, motion_status);
162 // Rotate motion axis to the correct direction
163 right_motion.accel_x = -right_motion.accel_x;
164 right_motion.accel_y = -right_motion.accel_y;
165 right_motion.gyro_z = -right_motion.gyro_z;
166 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
167 }
168}
169
170void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
171 const MotionStatus& motion_status) {
172 static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
173 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
174 Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
175 Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
176 Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
177 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
178 Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
179 };
180
181 const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
182 (input.button_input[1] << 16));
183 for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
184 const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
185 const int button = static_cast<int>(pro_buttons[i]);
186 callbacks.on_button_data(button, button_status);
187 }
188
189 const u16 raw_left_axis_x =
190 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
191 const u16 raw_left_axis_y =
192 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
193 const u16 raw_right_axis_x =
194 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
195 const u16 raw_right_axis_y =
196 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
197
198 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
199 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
200 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
201 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
202 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
203 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
204 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
205 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
206
207 if (motion_status.is_enabled) {
208 auto pro_motion = GetMotionInput(input, motion_status);
209 pro_motion.gyro_x = -pro_motion.gyro_x;
210 pro_motion.accel_y = -pro_motion.accel_y;
211 pro_motion.accel_z = -pro_motion.accel_z;
212 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
213 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
214 }
215}
216
217void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
218 static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
219 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
220 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
221 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
222 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
223 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
224 Joycon::PasivePadButton::StickL,
225 };
226
227 for (auto left_button : left_buttons) {
228 const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
229 const int button = static_cast<int>(left_button);
230 callbacks.on_button_data(button, button_status);
231 }
232}
233
234void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
235 static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
236 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
237 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
238 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
239 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
240 Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
241 Joycon::PasivePadButton::StickR,
242 };
243
244 for (auto right_button : right_buttons) {
245 const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
246 const int button = static_cast<int>(right_button);
247 callbacks.on_button_data(button, button_status);
248 }
249}
250
251void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
252 static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
253 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
254 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
255 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
256 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
257 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
258 Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
259 Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
260 };
261
262 for (auto pro_button : pro_buttons) {
263 const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
264 const int button = static_cast<int>(pro_button);
265 callbacks.on_button_data(button, button_status);
266 }
267}
268
269f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
270 const f32 value = static_cast<f32>(raw_value - calibration.center);
271 if (value > 0.0f) {
272 return value / calibration.max;
273 }
274 return value / calibration.min;
275}
276
277f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
278 AccelerometerSensitivity sensitivity) const {
279 const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
280 switch (sensitivity) {
281 case Joycon::AccelerometerSensitivity::G2:
282 return value / 4.0f;
283 case Joycon::AccelerometerSensitivity::G4:
284 return value / 2.0f;
285 case Joycon::AccelerometerSensitivity::G8:
286 return value;
287 case Joycon::AccelerometerSensitivity::G16:
288 return value * 2.0f;
289 }
290 return value;
291}
292
293f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
294 GyroSensitivity sensitivity) const {
295 const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
296 switch (sensitivity) {
297 case Joycon::GyroSensitivity::DPS250:
298 return value / 8.0f;
299 case Joycon::GyroSensitivity::DPS500:
300 return value / 4.0f;
301 case Joycon::GyroSensitivity::DPS1000:
302 return value / 2.0f;
303 case Joycon::GyroSensitivity::DPS2000:
304 return value;
305 }
306 return value;
307}
308
309s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
310 const InputReportActive& input) const {
311 return input.motion_input[(sensor * 3) + axis];
312}
313
314MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
315 const MotionStatus& motion_status) const {
316 MotionData motion{};
317 const auto& accel_cal = motion_calibration.accelerometer;
318 const auto& gyro_cal = motion_calibration.gyro;
319 const s16 raw_accel_x = input.motion_input[1];
320 const s16 raw_accel_y = input.motion_input[0];
321 const s16 raw_accel_z = input.motion_input[2];
322 const s16 raw_gyro_x = input.motion_input[4];
323 const s16 raw_gyro_y = input.motion_input[3];
324 const s16 raw_gyro_z = input.motion_input[5];
325
326 motion.delta_timestamp = motion_status.delta_time;
327 motion.accel_x =
328 GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
329 motion.accel_y =
330 GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
331 motion.accel_z =
332 GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
333 motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
334 motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
335 motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
336
337 // TODO(German77): Return all three samples data
338 return motion;
339}
340
341} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
new file mode 100644
index 000000000..354d41dad
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -0,0 +1,81 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <functional>
12#include <span>
13
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18// Handles input packages and triggers the corresponding input events
19class JoyconPoller {
20public:
21 JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
22 JoyStickCalibration right_stick_calibration_,
23 MotionCalibration motion_calibration_);
24
25 void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
26
27 /// Handles data from passive packages
28 void ReadPassiveMode(std::span<u8> buffer);
29
30 /// Handles data from active packages
31 void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
32 const RingStatus& ring_status);
33
34 /// Handles data from nfc or ir packages
35 void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
36
37 void UpdateColor(const Color& color);
38 void UpdateRing(s16 value, const RingStatus& ring_status);
39 void UpdateAmiibo(const std::vector<u8>& amiibo_data);
40 void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
41
42private:
43 void UpdateActiveLeftPadInput(const InputReportActive& input,
44 const MotionStatus& motion_status);
45 void UpdateActiveRightPadInput(const InputReportActive& input,
46 const MotionStatus& motion_status);
47 void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
48
49 void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
50 void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
51 void UpdatePasiveProPadInput(const InputReportPassive& buffer);
52
53 /// Returns a calibrated joystick axis from raw axis data
54 f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
55
56 /// Returns a calibrated accelerometer axis from raw motion data
57 f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
58 AccelerometerSensitivity sensitivity) const;
59
60 /// Returns a calibrated gyro axis from raw motion data
61 f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
62 GyroSensitivity sensitivity) const;
63
64 /// Returns a raw motion value from a buffer
65 s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
66
67 /// Returns motion data from a buffer
68 MotionData GetMotionInput(const InputReportActive& input,
69 const MotionStatus& motion_status) const;
70
71 ControllerType device_type{};
72
73 // Device calibration
74 JoyStickCalibration left_stick_calibration{};
75 JoyStickCalibration right_stick_calibration{};
76 MotionCalibration motion_calibration{};
77
78 Joycon::JoyconCallbacks callbacks{};
79};
80
81} // namespace InputCommon::Joycon
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 000000000..12f81309e
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -0,0 +1,117 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/ringcon.h"
6
7namespace InputCommon::Joycon {
8
9RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(std::move(handle)) {}
11
12DriverResult RingConProtocol::EnableRingCon() {
13 LOG_DEBUG(Input, "Enable Ringcon");
14 ScopedSetBlocking sb(this);
15 DriverResult result{DriverResult::Success};
16
17 if (result == DriverResult::Success) {
18 result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
19 }
20 if (result == DriverResult::Success) {
21 result = EnableMCU(true);
22 }
23 if (result == DriverResult::Success) {
24 const MCUConfig config{
25 .command = MCUCommand::ConfigureMCU,
26 .sub_command = MCUSubCommand::SetDeviceMode,
27 .mode = MCUMode::Standby,
28 .crc = {},
29 };
30 result = ConfigureMCU(config);
31 }
32
33 return result;
34}
35
36DriverResult RingConProtocol::DisableRingCon() {
37 LOG_DEBUG(Input, "Disable RingCon");
38 ScopedSetBlocking sb(this);
39 DriverResult result{DriverResult::Success};
40
41 if (result == DriverResult::Success) {
42 result = EnableMCU(false);
43 }
44
45 is_enabled = false;
46
47 return result;
48}
49
50DriverResult RingConProtocol::StartRingconPolling() {
51 LOG_DEBUG(Input, "Enable Ringcon");
52 ScopedSetBlocking sb(this);
53 DriverResult result{DriverResult::Success};
54 bool is_connected = false;
55
56 if (result == DriverResult::Success) {
57 result = IsRingConnected(is_connected);
58 }
59 if (result == DriverResult::Success && is_connected) {
60 LOG_INFO(Input, "Ringcon detected");
61 result = ConfigureRing();
62 }
63 if (result == DriverResult::Success) {
64 is_enabled = true;
65 }
66
67 return result;
68}
69
70DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
71 LOG_DEBUG(Input, "IsRingConnected");
72 constexpr std::size_t max_tries = 28;
73 constexpr u8 ring_controller_id = 0x20;
74 std::vector<u8> output;
75 std::size_t tries = 0;
76 is_connected = false;
77
78 do {
79 std::array<u8, 1> empty_data{};
80 const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
81
82 if (result != DriverResult::Success) {
83 return result;
84 }
85
86 if (tries++ >= max_tries) {
87 return DriverResult::NoDeviceDetected;
88 }
89 } while (output[16] != ring_controller_id);
90
91 is_connected = true;
92 return DriverResult::Success;
93}
94
95DriverResult RingConProtocol::ConfigureRing() {
96 LOG_DEBUG(Input, "ConfigureRing");
97
98 static constexpr std::array<u8, 37> ring_config{
99 0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
100 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
101 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
102
103 const DriverResult result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config);
104
105 if (result != DriverResult::Success) {
106 return result;
107 }
108
109 static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
110 return SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data);
111}
112
113bool RingConProtocol::IsEnabled() const {
114 return is_enabled;
115}
116
117} // 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 000000000..6e858f3fc
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RingConProtocol final : private JoyconCommonProtocol {
19public:
20 explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRingCon();
23
24 DriverResult DisableRingCon();
25
26 DriverResult StartRingconPolling();
27
28 bool IsEnabled() const;
29
30private:
31 DriverResult IsRingConnected(bool& is_connected);
32
33 DriverResult ConfigureRing();
34
35 bool is_enabled{};
36};
37
38} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
new file mode 100644
index 000000000..63b60c946
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <cmath>
6
7#include "common/logging/log.h"
8#include "input_common/helpers/joycon_protocol/rumble.h"
9
10namespace InputCommon::Joycon {
11
12RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
13 : JoyconCommonProtocol(std::move(handle)) {}
14
15DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
16 LOG_DEBUG(Input, "Enable Rumble");
17 ScopedSetBlocking sb(this);
18 const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
19 return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
20}
21
22DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
23 std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};
24
25 if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
26 return SendVibrationReport(DefaultVibrationBuffer);
27 }
28
29 // Protect joycons from damage from strong vibrations
30 const f32 clamp_amplitude =
31 1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
32
33 const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
34 const u8 encoded_high_amplitude =
35 EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
36 const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
37 const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
38
39 buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
40 buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
41 buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
42 buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
43
44 // Duplicate rumble for now
45 buffer[4] = buffer[0];
46 buffer[5] = buffer[1];
47 buffer[6] = buffer[2];
48 buffer[7] = buffer[3];
49
50 return SendVibrationReport(buffer);
51}
52
53u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
54 const u8 new_frequency =
55 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
56 return static_cast<u16>((new_frequency - 0x60) * 4);
57}
58
59u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
60 const u8 new_frequency =
61 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
62 return static_cast<u8>(new_frequency - 0x40);
63}
64
65u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
66 // More information about these values can be found here:
67 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
68
69 static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
70 std::pair<f32, int>{0.0f, 0x0},
71 {0.01f, 0x2},
72 {0.012f, 0x4},
73 {0.014f, 0x6},
74 {0.017f, 0x8},
75 {0.02f, 0x0a},
76 {0.024f, 0x0c},
77 {0.028f, 0x0e},
78 {0.033f, 0x10},
79 {0.04f, 0x12},
80 {0.047f, 0x14},
81 {0.056f, 0x16},
82 {0.067f, 0x18},
83 {0.08f, 0x1a},
84 {0.095f, 0x1c},
85 {0.112f, 0x1e},
86 {0.117f, 0x20},
87 {0.123f, 0x22},
88 {0.128f, 0x24},
89 {0.134f, 0x26},
90 {0.14f, 0x28},
91 {0.146f, 0x2a},
92 {0.152f, 0x2c},
93 {0.159f, 0x2e},
94 {0.166f, 0x30},
95 {0.173f, 0x32},
96 {0.181f, 0x34},
97 {0.189f, 0x36},
98 {0.198f, 0x38},
99 {0.206f, 0x3a},
100 {0.215f, 0x3c},
101 {0.225f, 0x3e},
102 {0.23f, 0x40},
103 {0.235f, 0x42},
104 {0.24f, 0x44},
105 {0.245f, 0x46},
106 {0.251f, 0x48},
107 {0.256f, 0x4a},
108 {0.262f, 0x4c},
109 {0.268f, 0x4e},
110 {0.273f, 0x50},
111 {0.279f, 0x52},
112 {0.286f, 0x54},
113 {0.292f, 0x56},
114 {0.298f, 0x58},
115 {0.305f, 0x5a},
116 {0.311f, 0x5c},
117 {0.318f, 0x5e},
118 {0.325f, 0x60},
119 {0.332f, 0x62},
120 {0.34f, 0x64},
121 {0.347f, 0x66},
122 {0.355f, 0x68},
123 {0.362f, 0x6a},
124 {0.37f, 0x6c},
125 {0.378f, 0x6e},
126 {0.387f, 0x70},
127 {0.395f, 0x72},
128 {0.404f, 0x74},
129 {0.413f, 0x76},
130 {0.422f, 0x78},
131 {0.431f, 0x7a},
132 {0.44f, 0x7c},
133 {0.45f, 0x7e},
134 {0.46f, 0x80},
135 {0.47f, 0x82},
136 {0.48f, 0x84},
137 {0.491f, 0x86},
138 {0.501f, 0x88},
139 {0.512f, 0x8a},
140 {0.524f, 0x8c},
141 {0.535f, 0x8e},
142 {0.547f, 0x90},
143 {0.559f, 0x92},
144 {0.571f, 0x94},
145 {0.584f, 0x96},
146 {0.596f, 0x98},
147 {0.609f, 0x9a},
148 {0.623f, 0x9c},
149 {0.636f, 0x9e},
150 {0.65f, 0xa0},
151 {0.665f, 0xa2},
152 {0.679f, 0xa4},
153 {0.694f, 0xa6},
154 {0.709f, 0xa8},
155 {0.725f, 0xaa},
156 {0.741f, 0xac},
157 {0.757f, 0xae},
158 {0.773f, 0xb0},
159 {0.79f, 0xb2},
160 {0.808f, 0xb4},
161 {0.825f, 0xb6},
162 {0.843f, 0xb8},
163 {0.862f, 0xba},
164 {0.881f, 0xbc},
165 {0.9f, 0xbe},
166 {0.92f, 0xc0},
167 {0.94f, 0xc2},
168 {0.96f, 0xc4},
169 {0.981f, 0xc6},
170 {1.003f, 0xc8},
171 };
172
173 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
174 if (amplitude <= amplitude_value) {
175 return static_cast<u8>(code);
176 }
177 }
178
179 return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
180}
181
182u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
183 // More information about these values can be found here:
184 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
185
186 static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
187 std::pair<f32, int>{0.0f, 0x0040},
188 {0.01f, 0x8040},
189 {0.012f, 0x0041},
190 {0.014f, 0x8041},
191 {0.017f, 0x0042},
192 {0.02f, 0x8042},
193 {0.024f, 0x0043},
194 {0.028f, 0x8043},
195 {0.033f, 0x0044},
196 {0.04f, 0x8044},
197 {0.047f, 0x0045},
198 {0.056f, 0x8045},
199 {0.067f, 0x0046},
200 {0.08f, 0x8046},
201 {0.095f, 0x0047},
202 {0.112f, 0x8047},
203 {0.117f, 0x0048},
204 {0.123f, 0x8048},
205 {0.128f, 0x0049},
206 {0.134f, 0x8049},
207 {0.14f, 0x004a},
208 {0.146f, 0x804a},
209 {0.152f, 0x004b},
210 {0.159f, 0x804b},
211 {0.166f, 0x004c},
212 {0.173f, 0x804c},
213 {0.181f, 0x004d},
214 {0.189f, 0x804d},
215 {0.198f, 0x004e},
216 {0.206f, 0x804e},
217 {0.215f, 0x004f},
218 {0.225f, 0x804f},
219 {0.23f, 0x0050},
220 {0.235f, 0x8050},
221 {0.24f, 0x0051},
222 {0.245f, 0x8051},
223 {0.251f, 0x0052},
224 {0.256f, 0x8052},
225 {0.262f, 0x0053},
226 {0.268f, 0x8053},
227 {0.273f, 0x0054},
228 {0.279f, 0x8054},
229 {0.286f, 0x0055},
230 {0.292f, 0x8055},
231 {0.298f, 0x0056},
232 {0.305f, 0x8056},
233 {0.311f, 0x0057},
234 {0.318f, 0x8057},
235 {0.325f, 0x0058},
236 {0.332f, 0x8058},
237 {0.34f, 0x0059},
238 {0.347f, 0x8059},
239 {0.355f, 0x005a},
240 {0.362f, 0x805a},
241 {0.37f, 0x005b},
242 {0.378f, 0x805b},
243 {0.387f, 0x005c},
244 {0.395f, 0x805c},
245 {0.404f, 0x005d},
246 {0.413f, 0x805d},
247 {0.422f, 0x005e},
248 {0.431f, 0x805e},
249 {0.44f, 0x005f},
250 {0.45f, 0x805f},
251 {0.46f, 0x0060},
252 {0.47f, 0x8060},
253 {0.48f, 0x0061},
254 {0.491f, 0x8061},
255 {0.501f, 0x0062},
256 {0.512f, 0x8062},
257 {0.524f, 0x0063},
258 {0.535f, 0x8063},
259 {0.547f, 0x0064},
260 {0.559f, 0x8064},
261 {0.571f, 0x0065},
262 {0.584f, 0x8065},
263 {0.596f, 0x0066},
264 {0.609f, 0x8066},
265 {0.623f, 0x0067},
266 {0.636f, 0x8067},
267 {0.65f, 0x0068},
268 {0.665f, 0x8068},
269 {0.679f, 0x0069},
270 {0.694f, 0x8069},
271 {0.709f, 0x006a},
272 {0.725f, 0x806a},
273 {0.741f, 0x006b},
274 {0.757f, 0x806b},
275 {0.773f, 0x006c},
276 {0.79f, 0x806c},
277 {0.808f, 0x006d},
278 {0.825f, 0x806d},
279 {0.843f, 0x006e},
280 {0.862f, 0x806e},
281 {0.881f, 0x006f},
282 {0.9f, 0x806f},
283 {0.92f, 0x0070},
284 {0.94f, 0x8070},
285 {0.96f, 0x0071},
286 {0.981f, 0x8071},
287 {1.003f, 0x0072},
288 };
289
290 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
291 if (amplitude <= amplitude_value) {
292 return static_cast<u16>(code);
293 }
294 }
295
296 return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h
new file mode 100644
index 000000000..6c12b7925
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RumbleProtocol final : private JoyconCommonProtocol {
19public:
20 explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRumble(bool is_enabled);
23
24 DriverResult SendVibration(const VibrationValue& vibration);
25
26private:
27 u16 EncodeHighFrequency(f32 frequency) const;
28 u8 EncodeLowFrequency(f32 frequency) const;
29 u8 EncodeHighAmplitude(f32 amplitude) const;
30 u16 EncodeLowAmplitude(f32 amplitude) const;
31};
32
33} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp
index f3a0b3419..096c23b07 100644
--- a/src/input_common/helpers/stick_from_buttons.cpp
+++ b/src/input_common/helpers/stick_from_buttons.cpp
@@ -11,6 +11,11 @@ namespace InputCommon {
11 11
12class Stick final : public Common::Input::InputDevice { 12class Stick final : public Common::Input::InputDevice {
13public: 13public:
14 // Some games such as EARTH DEFENSE FORCE: WORLD BROTHERS
15 // do not play nicely with the theoretical maximum range.
16 // Using a value one lower from the maximum emulates real stick behavior.
17 static constexpr float MAX_RANGE = 32766.0f / 32767.0f;
18
14 using Button = std::unique_ptr<Common::Input::InputDevice>; 19 using Button = std::unique_ptr<Common::Input::InputDevice>;
15 20
16 Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_, 21 Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_,
@@ -196,7 +201,7 @@ public:
196 } 201 }
197 202
198 void UpdateStatus() { 203 void UpdateStatus() {
199 const float coef = modifier_status.value ? modifier_scale : 1.0f; 204 const float coef = modifier_status.value ? modifier_scale : MAX_RANGE;
200 205
201 bool r = right_status; 206 bool r = right_status;
202 bool l = left_status; 207 bool l = left_status;
@@ -290,7 +295,7 @@ public:
290 if (down_status) { 295 if (down_status) {
291 --y; 296 --y;
292 } 297 }
293 const float coef = modifier_status.value ? modifier_scale : 1.0f; 298 const float coef = modifier_status.value ? modifier_scale : MAX_RANGE;
294 status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF); 299 status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
295 status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF); 300 status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
296 return status; 301 return status;