summaryrefslogtreecommitdiff
path: root/src/input_common/helpers/joycon_driver.cpp
diff options
context:
space:
mode:
authorGravatar liamwhite2023-01-24 09:29:37 -0500
committerGravatar GitHub2023-01-24 09:29:37 -0500
commita68af583ea378b48e2ed5a19f519a815ba89e40f (patch)
tree2983c14a7d4bc2797259c7d97462a439bec629f3 /src/input_common/helpers/joycon_driver.cpp
parentMerge pull request #9555 from abouvier/catch2-update (diff)
parentcore: hid: Make use of SCOPE_EXIT and SCOPE_GUARD where applicable (diff)
downloadyuzu-a68af583ea378b48e2ed5a19f519a815ba89e40f.tar.gz
yuzu-a68af583ea378b48e2ed5a19f519a815ba89e40f.tar.xz
yuzu-a68af583ea378b48e2ed5a19f519a815ba89e40f.zip
Merge pull request #9492 from german77/joycon_release
Input_common: Implement custom joycon driver v2
Diffstat (limited to 'src/input_common/helpers/joycon_driver.cpp')
-rw-r--r--src/input_common/helpers/joycon_driver.cpp572
1 files changed, 572 insertions, 0 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..4159e5717
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,572 @@
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->GetColor(color);
90 if (handle_device_type == ControllerType::Pro) {
91 // Some 3rd party controllers aren't pro controllers
92 generic_protocol->GetControllerType(device_type);
93 } else {
94 device_type = handle_device_type;
95 }
96 generic_protocol->GetSerialNumber(serial_number);
97 supported_features = GetSupportedFeatures();
98
99 // Get Calibration data
100 calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
101 calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
102 calibration_protocol->GetImuCalibration(motion_calibration);
103
104 // Set led status
105 generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
106
107 // Apply HW configuration
108 SetPollingMode();
109
110 // Initialize joycon poller
111 joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
112 right_stick_calibration, motion_calibration);
113
114 // Start pooling for data
115 is_connected = true;
116 if (!input_thread_running) {
117 input_thread =
118 std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
119 }
120
121 disable_input_thread = false;
122 return DriverResult::Success;
123}
124
125void JoyconDriver::InputThread(std::stop_token stop_token) {
126 LOG_INFO(Input, "Joycon Adapter input thread started");
127 Common::SetCurrentThreadName("JoyconInput");
128 input_thread_running = true;
129
130 // Max update rate is 5ms, ensure we are always able to read a bit faster
131 constexpr int ThreadDelay = 2;
132 std::vector<u8> buffer(MaxBufferSize);
133
134 while (!stop_token.stop_requested()) {
135 int status = 0;
136
137 if (!IsInputThreadValid()) {
138 input_thread.request_stop();
139 continue;
140 }
141
142 // By disabling the input thread we can ensure custom commands will succeed as no package is
143 // skipped
144 if (!disable_input_thread) {
145 status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
146 ThreadDelay);
147 } else {
148 std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
149 }
150
151 if (IsPayloadCorrect(status, buffer)) {
152 OnNewData(buffer);
153 }
154
155 std::this_thread::yield();
156 }
157
158 is_connected = false;
159 input_thread_running = false;
160 LOG_INFO(Input, "Joycon Adapter input thread stopped");
161}
162
163void JoyconDriver::OnNewData(std::span<u8> buffer) {
164 const auto report_mode = static_cast<InputReport>(buffer[0]);
165
166 // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
167 // experience
168 switch (report_mode) {
169 case InputReport::STANDARD_FULL_60HZ:
170 case InputReport::NFC_IR_MODE_60HZ:
171 case InputReport::SIMPLE_HID_MODE: {
172 const auto now = std::chrono::steady_clock::now();
173 const auto new_delta_time = static_cast<u64>(
174 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
175 delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
176 last_update = now;
177 joycon_poller->UpdateColor(color);
178 break;
179 }
180 default:
181 break;
182 }
183
184 const MotionStatus motion_status{
185 .is_enabled = motion_enabled,
186 .delta_time = delta_time,
187 .gyro_sensitivity = gyro_sensitivity,
188 .accelerometer_sensitivity = accelerometer_sensitivity,
189 };
190
191 // TODO: Remove this when calibration is properly loaded and not calculated
192 if (ring_connected && report_mode == InputReport::STANDARD_FULL_60HZ) {
193 InputReportActive data{};
194 memcpy(&data, buffer.data(), sizeof(InputReportActive));
195 calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
196 }
197
198 const RingStatus ring_status{
199 .is_enabled = ring_connected,
200 .default_value = ring_calibration.default_value,
201 .max_value = ring_calibration.max_value,
202 .min_value = ring_calibration.min_value,
203 };
204
205 if (irs_protocol->IsEnabled()) {
206 irs_protocol->RequestImage(buffer);
207 joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
208 }
209
210 if (nfc_protocol->IsEnabled()) {
211 if (amiibo_detected) {
212 if (!nfc_protocol->HasAmiibo()) {
213 joycon_poller->UpdateAmiibo({});
214 amiibo_detected = false;
215 return;
216 }
217 }
218
219 if (!amiibo_detected) {
220 std::vector<u8> data(0x21C);
221 const auto result = nfc_protocol->ScanAmiibo(data);
222 if (result == DriverResult::Success) {
223 joycon_poller->UpdateAmiibo(data);
224 amiibo_detected = true;
225 }
226 }
227 }
228
229 switch (report_mode) {
230 case InputReport::STANDARD_FULL_60HZ:
231 joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
232 break;
233 case InputReport::NFC_IR_MODE_60HZ:
234 joycon_poller->ReadNfcIRMode(buffer, motion_status);
235 break;
236 case InputReport::SIMPLE_HID_MODE:
237 joycon_poller->ReadPassiveMode(buffer);
238 break;
239 case InputReport::SUBCMD_REPLY:
240 LOG_DEBUG(Input, "Unhandled command reply");
241 break;
242 default:
243 LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
244 break;
245 }
246}
247
248DriverResult JoyconDriver::SetPollingMode() {
249 disable_input_thread = true;
250
251 rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
252
253 if (motion_enabled && supported_features.motion) {
254 generic_protocol->EnableImu(true);
255 generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
256 accelerometer_sensitivity, accelerometer_performance);
257 } else {
258 generic_protocol->EnableImu(false);
259 }
260
261 if (irs_protocol->IsEnabled()) {
262 irs_protocol->DisableIrs();
263 }
264
265 if (nfc_protocol->IsEnabled()) {
266 amiibo_detected = false;
267 nfc_protocol->DisableNfc();
268 }
269
270 if (ring_protocol->IsEnabled()) {
271 ring_connected = false;
272 ring_protocol->DisableRingCon();
273 }
274
275 if (irs_enabled && supported_features.irs) {
276 auto result = irs_protocol->EnableIrs();
277 if (result == DriverResult::Success) {
278 disable_input_thread = false;
279 return result;
280 }
281 irs_protocol->DisableIrs();
282 LOG_ERROR(Input, "Error enabling IRS");
283 }
284
285 if (nfc_enabled && supported_features.nfc) {
286 auto result = nfc_protocol->EnableNfc();
287 if (result == DriverResult::Success) {
288 result = nfc_protocol->StartNFCPollingMode();
289 }
290 if (result == DriverResult::Success) {
291 disable_input_thread = false;
292 return result;
293 }
294 nfc_protocol->DisableNfc();
295 LOG_ERROR(Input, "Error enabling NFC");
296 }
297
298 if (hidbus_enabled && supported_features.hidbus) {
299 auto result = ring_protocol->EnableRingCon();
300 if (result == DriverResult::Success) {
301 result = ring_protocol->StartRingconPolling();
302 }
303 if (result == DriverResult::Success) {
304 ring_connected = true;
305 disable_input_thread = false;
306 return result;
307 }
308 ring_connected = false;
309 ring_protocol->DisableRingCon();
310 LOG_ERROR(Input, "Error enabling Ringcon");
311 }
312
313 if (passive_enabled && supported_features.passive) {
314 const auto result = generic_protocol->EnablePassiveMode();
315 if (result == DriverResult::Success) {
316 disable_input_thread = false;
317 return result;
318 }
319 LOG_ERROR(Input, "Error enabling passive mode");
320 }
321
322 // Default Mode
323 const auto result = generic_protocol->EnableActiveMode();
324 if (result != DriverResult::Success) {
325 LOG_ERROR(Input, "Error enabling active mode");
326 }
327
328 disable_input_thread = false;
329 return result;
330}
331
332JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
333 SupportedFeatures features{
334 .passive = true,
335 .motion = true,
336 .vibration = true,
337 };
338
339 if (device_type == ControllerType::Right) {
340 features.nfc = true;
341 features.irs = true;
342 features.hidbus = true;
343 }
344
345 if (device_type == ControllerType::Pro) {
346 features.nfc = true;
347 }
348 return features;
349}
350
351bool JoyconDriver::IsInputThreadValid() const {
352 if (!is_connected.load()) {
353 return false;
354 }
355 if (hidapi_handle->handle == nullptr) {
356 return false;
357 }
358 // Controller is not responding. Terminate connection
359 if (error_counter > MaxErrorCount) {
360 return false;
361 }
362 return true;
363}
364
365bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
366 if (status <= -1) {
367 error_counter++;
368 return false;
369 }
370 // There's no new data
371 if (status == 0) {
372 return false;
373 }
374 // No reply ever starts with zero
375 if (buffer[0] == 0x00) {
376 error_counter++;
377 return false;
378 }
379 error_counter = 0;
380 return true;
381}
382
383DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
384 std::scoped_lock lock{mutex};
385 if (disable_input_thread) {
386 return DriverResult::HandleInUse;
387 }
388 return rumble_protocol->SendVibration(vibration);
389}
390
391DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
392 std::scoped_lock lock{mutex};
393 if (disable_input_thread) {
394 return DriverResult::HandleInUse;
395 }
396 return generic_protocol->SetLedPattern(led_pattern);
397}
398
399DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
400 std::scoped_lock lock{mutex};
401 if (disable_input_thread) {
402 return DriverResult::HandleInUse;
403 }
404 disable_input_thread = true;
405 const auto result = irs_protocol->SetIrsConfig(mode_, format_);
406 disable_input_thread = false;
407 return result;
408}
409
410DriverResult JoyconDriver::SetPasiveMode() {
411 std::scoped_lock lock{mutex};
412 motion_enabled = false;
413 hidbus_enabled = false;
414 nfc_enabled = false;
415 passive_enabled = true;
416 irs_enabled = false;
417 return SetPollingMode();
418}
419
420DriverResult JoyconDriver::SetActiveMode() {
421 if (is_ring_disabled_by_irs) {
422 is_ring_disabled_by_irs = false;
423 SetActiveMode();
424 return SetRingConMode();
425 }
426
427 std::scoped_lock lock{mutex};
428 motion_enabled = true;
429 hidbus_enabled = false;
430 nfc_enabled = false;
431 passive_enabled = false;
432 irs_enabled = false;
433 return SetPollingMode();
434}
435
436DriverResult JoyconDriver::SetIrMode() {
437 std::scoped_lock lock{mutex};
438
439 if (!supported_features.irs) {
440 return DriverResult::NotSupported;
441 }
442
443 if (ring_connected) {
444 is_ring_disabled_by_irs = true;
445 }
446
447 motion_enabled = false;
448 hidbus_enabled = false;
449 nfc_enabled = false;
450 passive_enabled = false;
451 irs_enabled = true;
452 return SetPollingMode();
453}
454
455DriverResult JoyconDriver::SetNfcMode() {
456 std::scoped_lock lock{mutex};
457
458 if (!supported_features.nfc) {
459 return DriverResult::NotSupported;
460 }
461
462 motion_enabled = true;
463 hidbus_enabled = false;
464 nfc_enabled = true;
465 passive_enabled = false;
466 irs_enabled = false;
467 return SetPollingMode();
468}
469
470DriverResult JoyconDriver::SetRingConMode() {
471 std::scoped_lock lock{mutex};
472
473 if (!supported_features.hidbus) {
474 return DriverResult::NotSupported;
475 }
476
477 motion_enabled = true;
478 hidbus_enabled = true;
479 nfc_enabled = false;
480 passive_enabled = false;
481 irs_enabled = false;
482
483 const auto result = SetPollingMode();
484
485 if (!ring_connected) {
486 return DriverResult::NoDeviceDetected;
487 }
488
489 return result;
490}
491
492bool JoyconDriver::IsConnected() const {
493 std::scoped_lock lock{mutex};
494 return is_connected.load();
495}
496
497bool JoyconDriver::IsVibrationEnabled() const {
498 std::scoped_lock lock{mutex};
499 return vibration_enabled;
500}
501
502FirmwareVersion JoyconDriver::GetDeviceVersion() const {
503 std::scoped_lock lock{mutex};
504 return version;
505}
506
507Color JoyconDriver::GetDeviceColor() const {
508 std::scoped_lock lock{mutex};
509 return color;
510}
511
512std::size_t JoyconDriver::GetDevicePort() const {
513 std::scoped_lock lock{mutex};
514 return port;
515}
516
517ControllerType JoyconDriver::GetDeviceType() const {
518 std::scoped_lock lock{mutex};
519 return device_type;
520}
521
522ControllerType JoyconDriver::GetHandleDeviceType() const {
523 std::scoped_lock lock{mutex};
524 return handle_device_type;
525}
526
527SerialNumber JoyconDriver::GetSerialNumber() const {
528 std::scoped_lock lock{mutex};
529 return serial_number;
530}
531
532SerialNumber JoyconDriver::GetHandleSerialNumber() const {
533 std::scoped_lock lock{mutex};
534 return handle_serial_number;
535}
536
537void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
538 joycon_poller->SetCallbacks(callbacks);
539}
540
541DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
542 ControllerType& controller_type) {
543 static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{
544 std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
545 {0x2007, ControllerType::Right},
546 };
547 constexpr u16 nintendo_vendor_id = 0x057e;
548
549 controller_type = ControllerType::None;
550 if (device_info->vendor_id != nintendo_vendor_id) {
551 return DriverResult::UnsupportedControllerType;
552 }
553
554 for (const auto& [product_id, type] : supported_devices) {
555 if (device_info->product_id == static_cast<u16>(product_id)) {
556 controller_type = type;
557 return Joycon::DriverResult::Success;
558 }
559 }
560 return Joycon::DriverResult::UnsupportedControllerType;
561}
562
563DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
564 SerialNumber& serial_number) {
565 if (device_info->serial_number == nullptr) {
566 return DriverResult::Unknown;
567 }
568 std::memcpy(&serial_number, device_info->serial_number, 15);
569 return Joycon::DriverResult::Success;
570}
571
572} // namespace InputCommon::Joycon