Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
CTCaer
GitHub Repository: CTCaer/hekate
Path: blob/master/bdk/usb/usb_gadget_hid.c
1476 views
1
/*
2
* USB Gadget HID driver for Tegra X1
3
*
4
* Copyright (c) 2019-2022 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 bool _jc_calibration(const jc_gamepad_rpt_t *jc_pad)
84
{
85
// Calibrate left stick.
86
if (jc_cal_ctx.cl_step != JC_CAL_MAX_STEPS)
87
{
88
if (jc_pad->conn_l
89
&& jc_pad->lstick_x > 0x400 && jc_pad->lstick_y > 0x400
90
&& jc_pad->lstick_x < 0xC00 && jc_pad->lstick_y < 0xC00)
91
{
92
jc_cal_ctx.cl_step++;
93
jc_cal_ctx.clx_max = jc_pad->lstick_x + 0x72;
94
jc_cal_ctx.clx_min = jc_pad->lstick_x - 0x72;
95
jc_cal_ctx.cly_max = jc_pad->lstick_y + 0x72;
96
jc_cal_ctx.cly_min = jc_pad->lstick_y - 0x72;
97
98
if (jc_cal_ctx.cl_step != JC_CAL_MAX_STEPS)
99
return false;
100
}
101
else
102
return false;
103
}
104
105
// Calibrate right stick.
106
if (jc_cal_ctx.cr_step != JC_CAL_MAX_STEPS)
107
{
108
if (jc_pad->conn_r
109
&& jc_pad->rstick_x > 0x400 && jc_pad->rstick_y > 0x400
110
&& jc_pad->rstick_x < 0xC00 && jc_pad->rstick_y < 0xC00)
111
{
112
jc_cal_ctx.cr_step++;
113
jc_cal_ctx.crx_max = jc_pad->rstick_x + 0x72;
114
jc_cal_ctx.crx_min = jc_pad->rstick_x - 0x72;
115
jc_cal_ctx.cry_max = jc_pad->rstick_y + 0x72;
116
jc_cal_ctx.cry_min = jc_pad->rstick_y - 0x72;
117
118
if (jc_cal_ctx.cr_step != JC_CAL_MAX_STEPS)
119
return false;
120
}
121
else
122
return false;
123
}
124
125
return true;
126
}
127
128
static int _jc_poll(gamepad_report_t *rpt)
129
{
130
static gamepad_report_t prev_rpt = {0};
131
132
// Poll Joy-Con.
133
jc_gamepad_rpt_t *jc_pad = joycon_poll();
134
135
if (!jc_pad)
136
return INPUT_POLL_NO_PACKET;
137
138
// Exit emulation if Left stick and Home are pressed.
139
if (jc_pad->l3 && jc_pad->home)
140
return INPUT_POLL_EXIT;
141
142
if (jc_cal_ctx.cl_step != JC_CAL_MAX_STEPS || jc_cal_ctx.cr_step != JC_CAL_MAX_STEPS)
143
{
144
if (!_jc_calibration(jc_pad))
145
return INPUT_POLL_NO_PACKET;
146
}
147
148
// Re-calibrate on disconnection.
149
if (!jc_pad->conn_l)
150
jc_cal_ctx.cl_step = 0;
151
if (!jc_pad->conn_r)
152
jc_cal_ctx.cr_step = 0;
153
154
// Calculate left analog stick.
155
if (jc_pad->lstick_x <= jc_cal_ctx.clx_max && jc_pad->lstick_x >= jc_cal_ctx.clx_min)
156
rpt->x = 0x7F;
157
else if (jc_pad->lstick_x > jc_cal_ctx.clx_max)
158
{
159
u16 x_raw = (jc_pad->lstick_x - jc_cal_ctx.clx_max) / 7;
160
if (x_raw > 0x7F)
161
x_raw = 0x7F;
162
rpt->x = 0x7F + x_raw;
163
}
164
else
165
{
166
u16 x_raw = (jc_cal_ctx.clx_min - jc_pad->lstick_x) / 7;
167
if (x_raw > 0x7F)
168
x_raw = 0x7F;
169
rpt->x = 0x7F - x_raw;
170
}
171
172
if (jc_pad->lstick_y <= jc_cal_ctx.cly_max && jc_pad->lstick_y >= jc_cal_ctx.cly_min)
173
rpt->y = 0x7F;
174
else if (jc_pad->lstick_y > jc_cal_ctx.cly_max)
175
{
176
u16 y_raw = (jc_pad->lstick_y - jc_cal_ctx.cly_max) / 7;
177
if (y_raw > 0x7F)
178
y_raw = 0x7F;
179
// Hoag has inverted Y axis.
180
if (!jc_pad->sio_mode)
181
rpt->y = 0x7F - y_raw;
182
else
183
rpt->y = 0x7F + y_raw;
184
}
185
else
186
{
187
u16 y_raw = (jc_cal_ctx.cly_min - jc_pad->lstick_y) / 7;
188
if (y_raw > 0x7F)
189
y_raw = 0x7F;
190
// Hoag has inverted Y axis.
191
if (!jc_pad->sio_mode)
192
rpt->y = 0x7F + y_raw;
193
else
194
rpt->y = 0x7F - y_raw;
195
}
196
197
// Calculate right analog stick.
198
if (jc_pad->rstick_x <= jc_cal_ctx.crx_max && jc_pad->rstick_x >= jc_cal_ctx.crx_min)
199
rpt->z = 0x7F;
200
else if (jc_pad->rstick_x > jc_cal_ctx.crx_max)
201
{
202
u16 x_raw = (jc_pad->rstick_x - jc_cal_ctx.crx_max) / 7;
203
if (x_raw > 0x7F)
204
x_raw = 0x7F;
205
rpt->z = 0x7F + x_raw;
206
}
207
else
208
{
209
u16 x_raw = (jc_cal_ctx.crx_min - jc_pad->rstick_x) / 7;
210
if (x_raw > 0x7F)
211
x_raw = 0x7F;
212
rpt->z = 0x7F - x_raw;
213
}
214
215
if (jc_pad->rstick_y <= jc_cal_ctx.cry_max && jc_pad->rstick_y >= jc_cal_ctx.cry_min)
216
rpt->rz = 0x7F;
217
else if (jc_pad->rstick_y > jc_cal_ctx.cry_max)
218
{
219
u16 y_raw = (jc_pad->rstick_y - jc_cal_ctx.cry_max) / 7;
220
if (y_raw > 0x7F)
221
y_raw = 0x7F;
222
// Hoag has inverted Y axis.
223
if (!jc_pad->sio_mode)
224
rpt->rz = 0x7F - y_raw;
225
else
226
rpt->rz = 0x7F + y_raw;
227
}
228
else
229
{
230
u16 y_raw = (jc_cal_ctx.cry_min - jc_pad->rstick_y) / 7;
231
if (y_raw > 0x7F)
232
y_raw = 0x7F;
233
// Hoag has inverted Y axis.
234
if (!jc_pad->sio_mode)
235
rpt->rz = 0x7F + y_raw;
236
else
237
rpt->rz = 0x7F - y_raw;
238
}
239
240
// Set D-pad.
241
switch ((jc_pad->buttons >> 16) & 0xF)
242
{
243
case 0: // none
244
rpt->hat = 0xF;
245
break;
246
case 1: // down
247
rpt->hat = 4;
248
break;
249
case 2: // up
250
rpt->hat = 0;
251
break;
252
case 4: // right
253
rpt->hat = 2;
254
break;
255
case 5: // down + right
256
rpt->hat = 3;
257
break;
258
case 6: // up + right
259
rpt->hat = 1;
260
break;
261
case 8: // left
262
rpt->hat = 6;
263
break;
264
case 9: // down + left
265
rpt->hat = 5;
266
break;
267
case 10: // up + left
268
rpt->hat = 7;
269
break;
270
default:
271
rpt->hat = 0xF;
272
break;
273
}
274
275
// Set buttons.
276
rpt->btn1 = jc_pad->b; // x.
277
rpt->btn2 = jc_pad->a; // a.
278
rpt->btn3 = jc_pad->y; // b.
279
rpt->btn4 = jc_pad->x; // y.
280
281
rpt->btn5 = jc_pad->l;
282
rpt->btn6 = jc_pad->r;
283
rpt->btn7 = jc_pad->zl;
284
rpt->btn8 = jc_pad->zr;
285
rpt->btn9 = jc_pad->minus;
286
rpt->btn10 = jc_pad->plus;
287
rpt->btn11 = jc_pad->l3;
288
rpt->btn12 = jc_pad->r3;
289
290
//rpt->btn13 = jc_pad->cap;
291
//rpt->btn14 = jc_pad->home;
292
293
if (!memcmp(rpt, &prev_rpt, sizeof(gamepad_report_t)))
294
return INPUT_POLL_NO_PACKET;
295
296
memcpy(&prev_rpt, rpt, sizeof(gamepad_report_t));
297
298
return INPUT_POLL_HAS_PACKET;
299
}
300
301
typedef struct _touchpad_report_t
302
{
303
u8 rpt_id;
304
u8 tip_switch:1;
305
u8 count:7;
306
307
u8 id;
308
309
//u16 z;
310
u16 x;
311
u16 y;
312
} __attribute__((packed)) touchpad_report_t;
313
314
static bool _fts_touch_read(touchpad_report_t *rpt)
315
{
316
static touch_event touchpad;
317
318
touch_poll(&touchpad);
319
320
rpt->rpt_id = 5;
321
rpt->count = 1;
322
323
// Decide touch enable.
324
switch (touchpad.type & STMFTS_MASK_EVENT_ID)
325
{
326
//case STMFTS_EV_MULTI_TOUCH_ENTER:
327
case STMFTS_EV_MULTI_TOUCH_MOTION:
328
rpt->x = touchpad.x;
329
rpt->y = touchpad.y;
330
//rpt->z = touchpad.z;
331
rpt->id = touchpad.fingers ? touchpad.fingers - 1 : 0;
332
rpt->tip_switch = 1;
333
break;
334
case STMFTS_EV_MULTI_TOUCH_LEAVE:
335
rpt->x = touchpad.x;
336
rpt->y = touchpad.y;
337
//rpt->z = touchpad.z;
338
rpt->id = touchpad.fingers ? touchpad.fingers - 1 : 0;
339
rpt->tip_switch = 0;
340
break;
341
case STMFTS_EV_NO_EVENT:
342
return false;
343
}
344
345
return true;
346
}
347
348
static u8 _hid_transfer_start(usb_ctxt_t *usbs, u32 len)
349
{
350
u8 status = usb_ops.usb_device_ep1_in_write((u8 *)USB_EP_BULK_IN_BUF_ADDR, len, NULL, USB_XFER_SYNCED_CMD);
351
if (status == USB_ERROR_XFER_ERROR)
352
{
353
usbs->set_text(usbs->label, "#FFDD00 Error:# EP IN transfer!");
354
if (usb_ops.usbd_flush_endpoint)
355
usb_ops.usbd_flush_endpoint(USB_EP_BULK_IN);
356
}
357
358
// Linux mitigation: If timed out, clear status.
359
if (status == USB_ERROR_TIMEOUT)
360
return 0;
361
362
return status;
363
}
364
365
static bool _hid_poll_jc(usb_ctxt_t *usbs)
366
{
367
int res = _jc_poll((gamepad_report_t *)USB_EP_BULK_IN_BUF_ADDR);
368
if (res == INPUT_POLL_EXIT)
369
return true;
370
371
// Send HID report.
372
if (res == INPUT_POLL_HAS_PACKET)
373
if (_hid_transfer_start(usbs, sizeof(gamepad_report_t)))
374
return true; // EP Error.
375
376
return false;
377
}
378
379
static bool _hid_poll_touch(usb_ctxt_t *usbs)
380
{
381
_fts_touch_read((touchpad_report_t *)USB_EP_BULK_IN_BUF_ADDR);
382
383
// Send HID report.
384
if (_hid_transfer_start(usbs, sizeof(touchpad_report_t)))
385
return true; // EP Error.
386
387
return false;
388
}
389
390
int usb_device_gadget_hid(usb_ctxt_t *usbs)
391
{
392
int res = 0;
393
u32 gadget_type;
394
u32 polling_time;
395
396
// Get USB Controller ops.
397
if (hw_get_chip_id() == GP_HIDREV_MAJOR_T210)
398
usb_device_get_ops(&usb_ops);
399
else
400
xusb_device_get_ops(&usb_ops);
401
402
if (usbs->type == USB_HID_GAMEPAD)
403
{
404
polling_time = 15000;
405
gadget_type = USB_GADGET_HID_GAMEPAD;
406
}
407
else
408
{
409
polling_time = 4000;
410
gadget_type = USB_GADGET_HID_TOUCHPAD;
411
}
412
413
usbs->set_text(usbs->label, "#C7EA46 Status:# Started USB");
414
415
if (usb_ops.usb_device_init())
416
{
417
usb_ops.usbd_end(false, true);
418
return 1;
419
}
420
421
usbs->set_text(usbs->label, "#C7EA46 Status:# Waiting for connection");
422
423
// Initialize Control Endpoint.
424
if (usb_ops.usb_device_enumerate(gadget_type))
425
goto error;
426
427
usbs->set_text(usbs->label, "#C7EA46 Status:# Waiting for HID report request");
428
429
if (usb_ops.usb_device_class_send_hid_report())
430
goto error;
431
432
usbs->set_text(usbs->label, "#C7EA46 Status:# Started HID emulation");
433
434
u32 timer_sys = get_tmr_ms() + 5000;
435
while (true)
436
{
437
u32 timer = get_tmr_us();
438
439
// Parse input device.
440
if (usbs->type == USB_HID_GAMEPAD)
441
{
442
if (_hid_poll_jc(usbs))
443
break;
444
}
445
else
446
{
447
if (_hid_poll_touch(usbs))
448
break;
449
}
450
451
// Check for suspended USB in case the cable was pulled.
452
if (usb_ops.usb_device_get_suspended())
453
break; // Disconnected.
454
455
// Handle control endpoint.
456
usb_ops.usbd_handle_ep0_ctrl_setup();
457
458
// Wait max gadget timing.
459
timer = get_tmr_us() - timer;
460
if (timer < polling_time)
461
usleep(polling_time - timer);
462
463
if (timer_sys < get_tmr_ms())
464
{
465
usbs->system_maintenance(true);
466
timer_sys = get_tmr_ms() + 5000;
467
}
468
}
469
470
usbs->set_text(usbs->label, "#C7EA46 Status:# HID ended");
471
goto exit;
472
473
error:
474
usbs->set_text(usbs->label, "#FFDD00 Error:# Timed out or canceled");
475
res = 1;
476
477
exit:
478
usb_ops.usbd_end(true, false);
479
480
return res;
481
}
482
483