Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
CTCaer
GitHub Repository: CTCaer/hekate
Path: blob/master/bdk/usb/usb_gadget_hid.c
3694 views
1
/*
2
* USB Gadget HID driver for Tegra X1
3
*
4
* Copyright (c) 2019-2026 CTCaer
5
*
6
* This program is free software; you can redistribute it and/or modify it
7
* under the terms and conditions of the GNU General Public License,
8
* version 2, as published by the Free Software Foundation.
9
*
10
* This program is distributed in the hope it will be useful, but WITHOUT
11
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13
* more details.
14
*
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
#include <string.h>
20
21
#include <usb/usbd.h>
22
#include <gfx_utils.h>
23
#include <input/joycon.h>
24
#include <input/touch.h>
25
#include <soc/hw_init.h>
26
#include <soc/timer.h>
27
#include <soc/t210.h>
28
29
#include <memory_map.h>
30
31
//#define DPRINTF(...) gfx_printf(__VA_ARGS__)
32
#define DPRINTF(...)
33
34
typedef struct _gamepad_report_t
35
{
36
u8 x;
37
u8 y;
38
u8 z;
39
u8 rz;
40
41
u8 hat:4;
42
u8 btn1:1;
43
u8 btn2:1;
44
u8 btn3:1;
45
u8 btn4:1;
46
47
u8 btn5:1;
48
u8 btn6:1;
49
u8 btn7:1;
50
u8 btn8:1;
51
u8 btn9:1;
52
u8 btn10:1;
53
u8 btn11:1;
54
u8 btn12:1;
55
} __attribute__((packed)) gamepad_report_t;
56
57
typedef struct _jc_cal_t
58
{
59
// 15ms * JC_CAL_MAX_STEPS = 240 ms.
60
#define JC_CAL_MAX_STEPS 16
61
u32 cl_step;
62
u32 cr_step;
63
64
u16 clx_max;
65
u16 clx_min;
66
u16 cly_max;
67
u16 cly_min;
68
u16 crx_max;
69
u16 crx_min;
70
u16 cry_max;
71
u16 cry_min;
72
} jc_cal_t;
73
74
enum {
75
INPUT_POLL_HAS_PACKET,
76
INPUT_POLL_NO_PACKET,
77
INPUT_POLL_EXIT,
78
};
79
80
static jc_cal_t jc_cal_ctx;
81
static usb_ops_t usb_ops;
82
83
static void *rpt_buffer = (u8 *)USB_EP_BULK_IN_BUF_ADDR;
84
85
static bool _jc_calibration(const jc_gamepad_rpt_t *jc_pad)
86
{
87
// Calibrate left stick.
88
if (jc_cal_ctx.cl_step != JC_CAL_MAX_STEPS)
89
{
90
if (jc_pad->conn_l
91
&& jc_pad->lstick_x > 0x400 && jc_pad->lstick_y > 0x400
92
&& jc_pad->lstick_x < 0xC00 && jc_pad->lstick_y < 0xC00)
93
{
94
jc_cal_ctx.cl_step++;
95
jc_cal_ctx.clx_max = jc_pad->lstick_x + 0x72;
96
jc_cal_ctx.clx_min = jc_pad->lstick_x - 0x72;
97
jc_cal_ctx.cly_max = jc_pad->lstick_y + 0x72;
98
jc_cal_ctx.cly_min = jc_pad->lstick_y - 0x72;
99
100
if (jc_cal_ctx.cl_step != JC_CAL_MAX_STEPS)
101
return false;
102
}
103
else
104
return false;
105
}
106
107
// Calibrate right stick.
108
if (jc_cal_ctx.cr_step != JC_CAL_MAX_STEPS)
109
{
110
if (jc_pad->conn_r
111
&& jc_pad->rstick_x > 0x400 && jc_pad->rstick_y > 0x400
112
&& jc_pad->rstick_x < 0xC00 && jc_pad->rstick_y < 0xC00)
113
{
114
jc_cal_ctx.cr_step++;
115
jc_cal_ctx.crx_max = jc_pad->rstick_x + 0x72;
116
jc_cal_ctx.crx_min = jc_pad->rstick_x - 0x72;
117
jc_cal_ctx.cry_max = jc_pad->rstick_y + 0x72;
118
jc_cal_ctx.cry_min = jc_pad->rstick_y - 0x72;
119
120
if (jc_cal_ctx.cr_step != JC_CAL_MAX_STEPS)
121
return false;
122
}
123
else
124
return false;
125
}
126
127
return true;
128
}
129
130
static int _jc_poll(gamepad_report_t *rpt)
131
{
132
static gamepad_report_t prev_rpt = {0};
133
134
// Poll Joy-Con.
135
jc_gamepad_rpt_t *jc_pad = joycon_poll();
136
137
if (!jc_pad)
138
return INPUT_POLL_NO_PACKET;
139
140
// Exit emulation if Left stick and Home are pressed.
141
if (jc_pad->l3 && jc_pad->home)
142
return INPUT_POLL_EXIT;
143
144
if (jc_cal_ctx.cl_step != JC_CAL_MAX_STEPS || jc_cal_ctx.cr_step != JC_CAL_MAX_STEPS)
145
{
146
if (!_jc_calibration(jc_pad))
147
return INPUT_POLL_NO_PACKET;
148
}
149
150
// Re-calibrate on disconnection.
151
if (!jc_pad->conn_l)
152
jc_cal_ctx.cl_step = 0;
153
if (!jc_pad->conn_r)
154
jc_cal_ctx.cr_step = 0;
155
156
// Calculate left analog stick.
157
if (jc_pad->lstick_x <= jc_cal_ctx.clx_max && jc_pad->lstick_x >= jc_cal_ctx.clx_min)
158
rpt->x = 0x7F;
159
else if (jc_pad->lstick_x > jc_cal_ctx.clx_max)
160
{
161
u16 x_raw = (jc_pad->lstick_x - jc_cal_ctx.clx_max) / 7;
162
if (x_raw > 0x7F)
163
x_raw = 0x7F;
164
rpt->x = 0x7F + x_raw;
165
}
166
else
167
{
168
u16 x_raw = (jc_cal_ctx.clx_min - jc_pad->lstick_x) / 7;
169
if (x_raw > 0x7F)
170
x_raw = 0x7F;
171
rpt->x = 0x7F - x_raw;
172
}
173
174
if (jc_pad->lstick_y <= jc_cal_ctx.cly_max && jc_pad->lstick_y >= jc_cal_ctx.cly_min)
175
rpt->y = 0x7F;
176
else if (jc_pad->lstick_y > jc_cal_ctx.cly_max)
177
{
178
u16 y_raw = (jc_pad->lstick_y - jc_cal_ctx.cly_max) / 7;
179
if (y_raw > 0x7F)
180
y_raw = 0x7F;
181
// Hoag has inverted Y axis.
182
if (!jc_pad->sio_mode)
183
rpt->y = 0x7F - y_raw;
184
else
185
rpt->y = 0x7F + y_raw;
186
}
187
else
188
{
189
u16 y_raw = (jc_cal_ctx.cly_min - jc_pad->lstick_y) / 7;
190
if (y_raw > 0x7F)
191
y_raw = 0x7F;
192
// Hoag has inverted Y axis.
193
if (!jc_pad->sio_mode)
194
rpt->y = 0x7F + y_raw;
195
else
196
rpt->y = 0x7F - y_raw;
197
}
198
199
// Calculate right analog stick.
200
if (jc_pad->rstick_x <= jc_cal_ctx.crx_max && jc_pad->rstick_x >= jc_cal_ctx.crx_min)
201
rpt->z = 0x7F;
202
else if (jc_pad->rstick_x > jc_cal_ctx.crx_max)
203
{
204
u16 x_raw = (jc_pad->rstick_x - jc_cal_ctx.crx_max) / 7;
205
if (x_raw > 0x7F)
206
x_raw = 0x7F;
207
rpt->z = 0x7F + x_raw;
208
}
209
else
210
{
211
u16 x_raw = (jc_cal_ctx.crx_min - jc_pad->rstick_x) / 7;
212
if (x_raw > 0x7F)
213
x_raw = 0x7F;
214
rpt->z = 0x7F - x_raw;
215
}
216
217
if (jc_pad->rstick_y <= jc_cal_ctx.cry_max && jc_pad->rstick_y >= jc_cal_ctx.cry_min)
218
rpt->rz = 0x7F;
219
else if (jc_pad->rstick_y > jc_cal_ctx.cry_max)
220
{
221
u16 y_raw = (jc_pad->rstick_y - jc_cal_ctx.cry_max) / 7;
222
if (y_raw > 0x7F)
223
y_raw = 0x7F;
224
// Hoag has inverted Y axis.
225
if (!jc_pad->sio_mode)
226
rpt->rz = 0x7F - y_raw;
227
else
228
rpt->rz = 0x7F + y_raw;
229
}
230
else
231
{
232
u16 y_raw = (jc_cal_ctx.cry_min - jc_pad->rstick_y) / 7;
233
if (y_raw > 0x7F)
234
y_raw = 0x7F;
235
// Hoag has inverted Y axis.
236
if (!jc_pad->sio_mode)
237
rpt->rz = 0x7F + y_raw;
238
else
239
rpt->rz = 0x7F - y_raw;
240
}
241
242
// Set D-pad.
243
switch ((jc_pad->buttons >> 16) & 0xF)
244
{
245
case 0: // none
246
rpt->hat = 0xF;
247
break;
248
case 1: // down
249
rpt->hat = 4;
250
break;
251
case 2: // up
252
rpt->hat = 0;
253
break;
254
case 4: // right
255
rpt->hat = 2;
256
break;
257
case 5: // down + right
258
rpt->hat = 3;
259
break;
260
case 6: // up + right
261
rpt->hat = 1;
262
break;
263
case 8: // left
264
rpt->hat = 6;
265
break;
266
case 9: // down + left
267
rpt->hat = 5;
268
break;
269
case 10: // up + left
270
rpt->hat = 7;
271
break;
272
default:
273
rpt->hat = 0xF;
274
break;
275
}
276
277
// Set buttons.
278
rpt->btn1 = jc_pad->b; // x.
279
rpt->btn2 = jc_pad->a; // a.
280
rpt->btn3 = jc_pad->y; // b.
281
rpt->btn4 = jc_pad->x; // y.
282
283
rpt->btn5 = jc_pad->l;
284
rpt->btn6 = jc_pad->r;
285
rpt->btn7 = jc_pad->zl;
286
rpt->btn8 = jc_pad->zr;
287
rpt->btn9 = jc_pad->minus;
288
rpt->btn10 = jc_pad->plus;
289
rpt->btn11 = jc_pad->l3;
290
rpt->btn12 = jc_pad->r3;
291
292
//rpt->btn13 = jc_pad->cap;
293
//rpt->btn14 = jc_pad->home;
294
295
if (!memcmp(rpt, &prev_rpt, sizeof(gamepad_report_t)))
296
return INPUT_POLL_NO_PACKET;
297
298
memcpy(&prev_rpt, rpt, sizeof(gamepad_report_t));
299
300
return INPUT_POLL_HAS_PACKET;
301
}
302
303
typedef struct _touchpad_report_t
304
{
305
u8 rpt_id;
306
u8 tip_switch:1;
307
u8 count:7;
308
309
u8 id;
310
311
//u16 z;
312
u16 x;
313
u16 y;
314
} __attribute__((packed)) touchpad_report_t;
315
316
static bool _fts_touch_read(touchpad_report_t *rpt)
317
{
318
static touch_event_t touchpad;
319
320
if (touch_poll(&touchpad))
321
return false;
322
323
rpt->rpt_id = 5;
324
rpt->count = 1;
325
326
// Decide touch enable.
327
if (touchpad.touch)
328
{
329
rpt->x = touchpad.x;
330
rpt->y = touchpad.y;
331
//rpt->z = touchpad.z;
332
rpt->id = touchpad.finger;
333
rpt->tip_switch = 1;
334
}
335
else
336
{
337
rpt->x = touchpad.x;
338
rpt->y = touchpad.y;
339
//rpt->z = touchpad.z;
340
rpt->id = touchpad.finger;
341
rpt->tip_switch = 0;
342
}
343
344
return true;
345
}
346
347
static u8 _hid_transfer_start(usb_ctxt_t *usbs, u32 len)
348
{
349
u8 status = usb_ops.usb_device_ep1_in_write(rpt_buffer, len, NULL, USB_XFER_SYNCED_CMD);
350
if (status == USB_ERROR_XFER_ERROR)
351
{
352
usbs->set_text(usbs->label, "#FFDD00 Error:# EP IN transfer!");
353
if (usb_ops.usbd_flush_endpoint)
354
usb_ops.usbd_flush_endpoint(USB_EP_BULK_IN);
355
}
356
357
// Linux mitigation: If timed out, clear status.
358
if (status == USB_ERROR_TIMEOUT)
359
return 0;
360
361
return status;
362
}
363
364
static bool _hid_poll_jc(usb_ctxt_t *usbs)
365
{
366
int res = _jc_poll(rpt_buffer);
367
if (res == INPUT_POLL_EXIT)
368
return true;
369
370
// Send HID report.
371
if (res == INPUT_POLL_HAS_PACKET || usbs->idle)
372
if (_hid_transfer_start(usbs, sizeof(gamepad_report_t)))
373
return true; // EP Error.
374
375
return false;
376
}
377
378
static bool _hid_poll_touch(usb_ctxt_t *usbs)
379
{
380
_fts_touch_read(rpt_buffer);
381
382
// Send HID report.
383
if (_hid_transfer_start(usbs, sizeof(touchpad_report_t)))
384
return true; // EP Error.
385
386
return false;
387
}
388
389
int usb_device_gadget_hid(usb_ctxt_t *usbs)
390
{
391
int res = 0;
392
u32 gadget_type;
393
u32 polling_time;
394
395
// Get USB Controller ops.
396
if (hw_get_chip_id() == GP_HIDREV_MAJOR_T210)
397
usb_device_get_ops(&usb_ops);
398
else
399
xusb_device_get_ops(&usb_ops);
400
401
// Always push packets by default.
402
//! TODO: For now only per polling rate or on change is supported.
403
usbs->idle = 1;
404
405
if (usbs->type == USB_HID_GAMEPAD)
406
{
407
polling_time = 15000;
408
gadget_type = USB_GADGET_HID_GAMEPAD;
409
}
410
else
411
{
412
polling_time = 4000;
413
gadget_type = USB_GADGET_HID_TOUCHPAD;
414
}
415
416
usbs->set_text(usbs->label, "#C7EA46 Status:# Started USB");
417
418
if (usb_ops.usb_device_init())
419
{
420
usb_ops.usbd_end(false, true);
421
return 1;
422
}
423
424
usbs->set_text(usbs->label, "#C7EA46 Status:# Waiting for connection");
425
426
// Initialize Control Endpoint.
427
if (usb_ops.usb_device_enumerate(gadget_type))
428
goto error;
429
430
usbs->set_text(usbs->label, "#C7EA46 Status:# Waiting for HID report request");
431
432
u32 rpt_size = usbs->type == USB_HID_GAMEPAD ? sizeof(gamepad_report_t) : sizeof(touchpad_report_t);
433
if (usb_ops.usb_device_class_send_hid_report(rpt_buffer, rpt_size))
434
goto error;
435
436
usbs->set_text(usbs->label, "#C7EA46 Status:# Started HID emulation");
437
438
u32 timer_sys = get_tmr_ms() + 5000;
439
while (true)
440
{
441
u32 timer = get_tmr_us();
442
443
// Check for suspended USB in case the cable was pulled.
444
if (usb_ops.usb_device_get_suspended())
445
break; // Disconnected.
446
447
// Handle control endpoint.
448
usb_ops.usbd_handle_ep0_ctrl_setup(&usbs->idle);
449
450
// Parse input device.
451
if (usbs->type == USB_HID_GAMEPAD)
452
{
453
if (_hid_poll_jc(usbs))
454
break;
455
}
456
else
457
{
458
if (_hid_poll_touch(usbs))
459
break;
460
}
461
462
// Wait max gadget timing.
463
timer = get_tmr_us() - timer;
464
if (timer < polling_time)
465
usleep(polling_time - timer);
466
467
if (timer_sys < get_tmr_ms())
468
{
469
usbs->system_maintenance(true);
470
timer_sys = get_tmr_ms() + 5000;
471
}
472
}
473
474
usbs->set_text(usbs->label, "#C7EA46 Status:# HID ended");
475
goto exit;
476
477
error:
478
usbs->set_text(usbs->label, "#FFDD00 Error:# Timed out or canceled");
479
res = 1;
480
481
exit:
482
usb_ops.usbd_end(true, false);
483
484
return res;
485
}
486
487