summaryrefslogtreecommitdiff
path: root/src/input_common/helpers/joycon_protocol/common_protocol.cpp
diff options
context:
space:
mode:
authorGravatar Narr the Reg2022-12-20 13:54:17 -0600
committerGravatar Narr the Reg2023-01-19 18:05:21 -0600
commit6aa6301acdf1c29b80ebfc7fca2aac326400760f (patch)
treec81414cfa80044843486e0e8a3346423c1771184 /src/input_common/helpers/joycon_protocol/common_protocol.cpp
parentservice: hid: Set led pattern and fix color detection (diff)
downloadyuzu-6aa6301acdf1c29b80ebfc7fca2aac326400760f.tar.gz
yuzu-6aa6301acdf1c29b80ebfc7fca2aac326400760f.tar.xz
yuzu-6aa6301acdf1c29b80ebfc7fca2aac326400760f.zip
input_common: Add joycon low level functions
Diffstat (limited to 'src/input_common/helpers/joycon_protocol/common_protocol.cpp')
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp286
1 files changed, 286 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..43a036e02
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -0,0 +1,286 @@
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{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::vector<u8> buffer{static_cast<u8>(report_mode)};
62 std::vector<u8> output;
63 return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer, output);
64}
65
66DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
67 const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
68
69 if (result == -1) {
70 return DriverResult::ErrorWritingData;
71 }
72
73 return DriverResult::Success;
74}
75
76DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
77 constexpr int timeout_mili = 100;
78 constexpr int MaxTries = 10;
79 int tries = 0;
80 output.resize(MaxSubCommandResponseSize);
81
82 do {
83 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
84 MaxSubCommandResponseSize, timeout_mili);
85
86 if (result < 1) {
87 LOG_ERROR(Input, "No response from joycon");
88 }
89 if (tries++ > MaxTries) {
90 return DriverResult::Timeout;
91 }
92 } while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
93
94 if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
95 return DriverResult::WrongReply;
96 }
97
98 return DriverResult::Success;
99}
100
101DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
102 std::vector<u8>& output) {
103 std::vector<u8> local_buffer(MaxResponseSize);
104
105 local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
106 local_buffer[1] = GetCounter();
107 local_buffer[10] = static_cast<u8>(sc);
108 for (std::size_t i = 0; i < buffer.size(); ++i) {
109 local_buffer[11 + i] = buffer[i];
110 }
111
112 auto result = SendData(local_buffer);
113
114 if (result != DriverResult::Success) {
115 return result;
116 }
117
118 result = GetSubCommandResponse(sc, output);
119
120 return DriverResult::Success;
121}
122
123DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
124 std::vector<u8> local_buffer(MaxResponseSize);
125
126 local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
127 local_buffer[1] = GetCounter();
128
129 memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
130
131 return SendData(local_buffer);
132}
133
134DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
135 constexpr std::size_t MaxTries = 10;
136 std::size_t tries = 0;
137 std::vector<u8> buffer = {0x00, 0x00, 0x00, 0x00, size};
138 std::vector<u8> local_buffer(size + 20);
139
140 buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
141 buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
142 do {
143 const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
144 if (result != DriverResult::Success) {
145 return result;
146 }
147
148 if (tries++ > MaxTries) {
149 return DriverResult::Timeout;
150 }
151 } while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
152
153 // Remove header from output
154 output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
155 return DriverResult::Success;
156}
157
158DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
159 std::vector<u8> output;
160
161 const std::vector<u8> mcu_state{static_cast<u8>(enable ? 1 : 0)};
162 const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state, output);
163
164 if (result != DriverResult::Success) {
165 LOG_ERROR(Input, "SendMCUData failed with error {}", result);
166 }
167
168 return result;
169}
170
171DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
172 LOG_DEBUG(Input, "ConfigureMCU");
173 std::vector<u8> output;
174
175 std::array<u8, sizeof(MCUConfig)> config_buffer;
176 memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
177 config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
178
179 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer, output);
180
181 if (result != DriverResult::Success) {
182 LOG_ERROR(Input, "Set MCU config failed with error {}", result);
183 }
184
185 return result;
186}
187
188DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
189 std::vector<u8>& output) {
190 const int report_mode = static_cast<u8>(report_mode_);
191 constexpr int TimeoutMili = 200;
192 constexpr int MaxTries = 9;
193 int tries = 0;
194 output.resize(0x170);
195
196 do {
197 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
198
199 if (result < 1) {
200 LOG_ERROR(Input, "No response from joycon attempt {}", tries);
201 }
202 if (tries++ > MaxTries) {
203 return DriverResult::Timeout;
204 }
205 } while (output[0] != report_mode || output[49] == 0xFF);
206
207 if (output[0] != report_mode || output[49] == 0xFF) {
208 return DriverResult::WrongReply;
209 }
210
211 return DriverResult::Success;
212}
213
214DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
215 std::span<const u8> buffer,
216 std::vector<u8>& output) {
217 std::vector<u8> local_buffer(MaxResponseSize);
218
219 local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
220 local_buffer[1] = GetCounter();
221 local_buffer[9] = static_cast<u8>(sc);
222 for (std::size_t i = 0; i < buffer.size(); ++i) {
223 local_buffer[10 + i] = buffer[i];
224 }
225
226 auto result = SendData(local_buffer);
227
228 if (result != DriverResult::Success) {
229 return result;
230 }
231
232 result = GetMCUDataResponse(report_mode, output);
233
234 return DriverResult::Success;
235}
236
237DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
238 std::vector<u8> output;
239 constexpr std::size_t MaxTries{8};
240 std::size_t tries{};
241
242 do {
243 const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
244 const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
245
246 if (result != DriverResult::Success) {
247 return result;
248 }
249
250 if (tries++ > MaxTries) {
251 return DriverResult::WrongReply;
252 }
253 } while (output[49] != 1 || output[56] != static_cast<u8>(mode));
254
255 return DriverResult::Success;
256}
257
258// crc-8-ccitt / polynomial 0x07 look up table
259static constexpr uint8_t mcu_crc8_table[256] = {
260 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
261 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
262 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
263 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
264 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
265 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
266 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
267 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
268 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
269 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
270 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
271 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
272 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
273 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
274 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
275 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
276
277u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
278 u8 crc8 = 0x0;
279
280 for (int i = 0; i < size; ++i) {
281 crc8 = mcu_crc8_table[(u8)(crc8 ^ buffer[i])];
282 }
283 return crc8;
284}
285
286} // namespace InputCommon::Joycon