bdk: joycon: make init stricter but relax timings

Additionally, if both Joy-Con are found, try to send rumble simultaneously.
This commit is contained in:
CTCaer
2026-01-20 20:03:38 +02:00
parent 3134af6e92
commit 74972a68df

View File

@@ -107,9 +107,11 @@ enum
{ {
JC_STATE_START = 0, JC_STATE_START = 0,
JC_STATE_HANDSHAKED = 1, JC_STATE_HANDSHAKED = 1,
JC_STATE_BRATE_CHANGED = 2, JC_STATE_INFO_PARSED = 2,
JC_STATE_BRATE_OK = 3, JC_STATE_BRATE_CHANGED = 3,
JC_STATE_INIT_DONE = 4 JC_STATE_HID_NO_CONN = 4,
JC_STATE_HID_CONN = 5,
JC_STATE_INIT_DONE = 6
}; };
enum enum
@@ -650,14 +652,12 @@ static void _jc_send_hid_cmd(jc_dev_t *jc, u8 subcmd, const u8 *data, u16 size)
if (send_l_rumble) if (send_l_rumble)
_jc_send_hid_output_rpt(&jc_l, hid_pkt, 0x10, false); _jc_send_hid_output_rpt(&jc_l, hid_pkt, 0x10, false);
if (send_r_rumble || send_l_rumble) msleep(15);
{
msleep(2); if (send_r_rumble)
if (send_r_rumble) _jc_rcv_pkt(&jc_r);
_jc_rcv_pkt(&jc_r); if (send_l_rumble)
if (send_l_rumble) _jc_rcv_pkt(&jc_l);
_jc_rcv_pkt(&jc_l);
}
// Send rumble. // Send rumble.
hid_pkt->cmd = JC_HID_RUMBLE_RPT; hid_pkt->cmd = JC_HID_RUMBLE_RPT;
@@ -785,23 +785,35 @@ static void _jc_parse_wired_init(jc_dev_t *jc, const jc_wired_hdr_t *pkt, int si
switch (pkt->subcmd) switch (pkt->subcmd)
{ {
case JC_WIRED_CMD_GET_INFO: case JC_WIRED_CMD_GET_INFO:
for (int i = 6; i > 0; i--) if (!pkt->status)
{
for (int i = 6; i > 0; i--)
jc->mac[6 - i] = payload[i]; jc->mac[6 - i] = payload[i];
jc->type = payload[0]; jc->type = payload[0];
jc->connected = true; jc->state = JC_STATE_INFO_PARSED;
}
break; break;
case JC_WIRED_CMD_SET_BRATE: case JC_WIRED_CMD_SET_BRATE:
jc->state = JC_STATE_BRATE_CHANGED; if (!pkt->status)
jc->state = JC_STATE_BRATE_CHANGED;
break; break;
case JC_WIRED_CMD_HID_DISC: case JC_WIRED_CMD_HID_DISC:
jc->state = JC_STATE_BRATE_OK; if (pkt->status == 0xF)
jc->state = JC_STATE_HID_NO_CONN;
break; break;
case JC_WIRED_CMD_HID_CONN: case JC_WIRED_CMD_HID_CONN:
if (!pkt->status)
jc->state = JC_STATE_HID_CONN;
break;
case JC_WIRED_CMD_SET_HIDRATE: case JC_WIRED_CMD_SET_HIDRATE:
// done. jc->state = JC_STATE_INIT_DONE;
jc->connected = true;
break;
default: default:
break; break;
} }
@@ -870,12 +882,13 @@ static void _jc_sio_uart_pkt_parse(jc_dev_t *jc, const jc_sio_in_rpt_t *pkt, int
switch (cmd) switch (cmd)
{ {
case JC_SIO_CMD_INIT: case JC_SIO_CMD_INIT:
jc->connected = pkt->status == 0; if (!pkt->status)
jc->state = JC_STATE_HANDSHAKED;
break; break;
case JC_SIO_CMD_VER_RPT: case JC_SIO_CMD_VER_RPT:
if (jc->connected) if (!pkt->status)
jc->connected = pkt->status == 0; jc->state = JC_STATE_HID_CONN;
break; break;
case JC_SIO_CMD_IAP_VER: case JC_SIO_CMD_IAP_VER:
@@ -1012,9 +1025,16 @@ set_mode:
static bool _jc_send_enable_rumble(jc_dev_t *jc) static bool _jc_send_enable_rumble(jc_dev_t *jc)
{ {
bool send_r_rumble = jc_r.connected && !jc_r.rumble_sent;
bool send_l_rumble = jc_l.connected && !jc_l.rumble_sent;
// Do not sent report yet if second Joy-Con is expected to be initialized.
if ((send_r_rumble && !jc_l.rumble_sent && jc_l.state == JC_STATE_HID_CONN) ||
(send_l_rumble && !jc_r.rumble_sent && jc_r.state == JC_STATE_HID_CONN))
return 1;
// Send init rumble or request nx pad status report. // Send init rumble or request nx pad status report.
if ((jc_r.connected && !jc_r.rumble_sent) || if (send_r_rumble || send_l_rumble)
(jc_l.connected && !jc_l.rumble_sent))
{ {
_jc_send_hid_cmd(jc, JC_HID_SUBCMD_SND_RUMBLE, NULL, 0); _jc_send_hid_cmd(jc, JC_HID_SUBCMD_SND_RUMBLE, NULL, 0);
@@ -1033,14 +1053,14 @@ static bool _jc_send_enable_rumble(jc_dev_t *jc)
static void _jc_req_status(jc_dev_t *jc) static void _jc_req_status(jc_dev_t *jc)
{ {
if (!jc->detected) if (!jc->connected)
return;
if (jc->last_status_req_time > get_tmr_ms())
return; return;
bool is_nxpad = !(jc->type & JC_ID_HORI) && !jc->sio_mode; bool is_nxpad = !(jc->type & JC_ID_HORI) && !jc->sio_mode;
if (jc->last_status_req_time > get_tmr_ms() || !jc->connected)
return;
// Init/maintenance for Joy-Con. // Init/maintenance for Joy-Con.
if (is_nxpad) if (is_nxpad)
{ {
@@ -1258,24 +1278,26 @@ static void _jc_conn_init(jc_dev_t *jc)
// Initialize uart to 1 megabaud and manual RTS. // Initialize uart to 1 megabaud and manual RTS.
uart_init(jc->uart, 1000000, UART_AO_TX_MN_RX); uart_init(jc->uart, 1000000, UART_AO_TX_MN_RX);
jc->state = JC_STATE_START;
if (!jc->sio_mode) if (!jc->sio_mode)
{ {
jc_gamepad.buttons = 0; jc_gamepad.buttons = 0;
jc->state = JC_STATE_START;
// Set TX and RTS inversion for Joycon. // Set TX and RTS inversion for Joycon.
uart_invert(jc->uart, true, UART_INVERT_TXD | UART_INVERT_RTS); uart_invert(jc->uart, true, UART_INVERT_TXD | UART_INVERT_RTS);
// Wake up the controller. // Initialize.
_joycon_send_raw(jc->uart, _jc_init_wake, sizeof(_jc_init_wake));
_jc_rcv_pkt(jc); // Clear RX FIFO.
// Do a handshake.
u32 retries = 10; u32 retries = 10;
while (retries && jc->state != JC_STATE_HANDSHAKED) while (retries && jc->state != JC_STATE_HANDSHAKED)
{ {
// Wake up the controller.
_joycon_send_raw(jc->uart, _jc_init_wake, sizeof(_jc_init_wake));
_jc_rcv_pkt(jc); // Clear RX FIFO.
// Do a handshake.
_joycon_send_raw(jc->uart, _jc_init_handshake, sizeof(_jc_init_handshake)); _joycon_send_raw(jc->uart, _jc_init_handshake, sizeof(_jc_init_handshake));
msleep(5); msleep(4);
_jc_rcv_pkt(jc); _jc_rcv_pkt(jc);
retries--; retries--;
} }
@@ -1288,6 +1310,9 @@ static void _jc_conn_init(jc_dev_t *jc)
msleep(2); msleep(2);
_jc_rcv_pkt(jc); _jc_rcv_pkt(jc);
if (jc->state != JC_STATE_INFO_PARSED)
goto out;
if (!(jc->type & JC_ID_HORI)) if (!(jc->type & JC_ID_HORI))
{ {
// Request 3 megabaud change. // Request 3 megabaud change.
@@ -1301,9 +1326,9 @@ static void _jc_conn_init(jc_dev_t *jc)
uart_init(jc->uart, 3000000, UART_AO_TX_MN_RX); uart_init(jc->uart, 3000000, UART_AO_TX_MN_RX);
uart_invert(jc->uart, true, UART_INVERT_TXD | UART_INVERT_RTS); uart_invert(jc->uart, true, UART_INVERT_TXD | UART_INVERT_RTS);
// Disconnect HID. // Make sure HID is disconnected and check connection. Reply expected after 30ms.
retries = 10; retries = 10;
while (retries && jc->state != JC_STATE_BRATE_OK) while (retries && jc->state != JC_STATE_HID_NO_CONN)
{ {
_joycon_send_raw(jc->uart, _jc_init_hid_disconnect, sizeof(_jc_init_hid_disconnect)); _joycon_send_raw(jc->uart, _jc_init_hid_disconnect, sizeof(_jc_init_hid_disconnect));
msleep(5); msleep(5);
@@ -1311,7 +1336,8 @@ static void _jc_conn_init(jc_dev_t *jc)
retries--; retries--;
} }
if (jc->state != JC_STATE_BRATE_OK) // Was connected before or no response. Do a reinit.
if (jc->state != JC_STATE_HID_NO_CONN)
goto out; goto out;
} }
@@ -1320,10 +1346,15 @@ static void _jc_conn_init(jc_dev_t *jc)
msleep(2); msleep(2);
_jc_rcv_pkt(jc); _jc_rcv_pkt(jc);
if (jc->state != JC_STATE_HID_CONN)
goto out;
// Set hid packet rate. // Set hid packet rate.
_joycon_send_raw(jc->uart, _jc_init_set_hid_rate, sizeof(_jc_init_set_hid_rate)); _joycon_send_raw(jc->uart, _jc_init_set_hid_rate, sizeof(_jc_init_set_hid_rate));
msleep(2); msleep(2);
_jc_rcv_pkt(jc); _jc_rcv_pkt(jc);
goto out; // Wait for set hid rate reply.
} }
else // Hori. Unset RTS inversion. else // Hori. Unset RTS inversion.
uart_invert(jc->uart, false, UART_INVERT_RTS); uart_invert(jc->uart, false, UART_INVERT_RTS);
@@ -1342,7 +1373,7 @@ static void _jc_conn_init(jc_dev_t *jc)
// Initialize the controller. // Initialize the controller.
u32 retries = 10; u32 retries = 10;
while (!jc->connected && retries) while (retries && jc->state != JC_STATE_HANDSHAKED)
{ {
_joycon_send_raw(jc->uart, _sio_init, sizeof(_sio_init)); _joycon_send_raw(jc->uart, _sio_init, sizeof(_sio_init));
msleep(5); msleep(5);
@@ -1350,17 +1381,26 @@ static void _jc_conn_init(jc_dev_t *jc)
retries--; retries--;
} }
if (!jc->connected) if (jc->state != JC_STATE_HANDSHAKED)
goto out; goto out;
// Set output report version. // Set output report version.
_joycon_send_raw(jc->uart, _sio_set_rpt_version, sizeof(_sio_set_rpt_version)); retries = 10;
msleep(5); while (retries && jc->state != JC_STATE_HID_CONN)
_jc_rcv_pkt(jc); {
_joycon_send_raw(jc->uart, _sio_set_rpt_version, sizeof(_sio_set_rpt_version));
msleep(5);
_jc_rcv_pkt(jc);
retries--;
}
if (jc->state != JC_STATE_HID_CONN)
goto out;
} }
// Initialization done. // Initialization done.
jc->state = JC_STATE_INIT_DONE; jc->state = JC_STATE_INIT_DONE;
jc->connected = true;
out: out:
jc->last_received_time = get_tmr_ms(); jc->last_received_time = get_tmr_ms();