summaryrefslogtreecommitdiff
path: root/src/input_common/helpers/joycon_protocol/common_protocol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/helpers/joycon_protocol/common_protocol.cpp')
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp299
1 files changed, 299 insertions, 0 deletions
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