From 7ad77cffb0513a164b3ff7eef73dce65b9d75057 Mon Sep 17 00:00:00 2001 From: "Such Meme, Many Skill" Date: Fri, 1 May 2020 22:42:49 +0200 Subject: [PATCH] Basic controller input --- source/hid/hid.c | 66 +++ source/hid/hid.h | 50 ++ source/hid/joycon.c | 807 ++++++++++++++++++++++++++++++++ source/hid/joycon.h | 98 ++++ source/power/regulator_5v.c | 76 +++ source/power/regulator_5v.h | 34 ++ source/tegraexplorer/gfx/menu.c | 35 +- source/tegraexplorer/mainmenu.c | 10 +- 8 files changed, 1162 insertions(+), 14 deletions(-) create mode 100644 source/hid/hid.c create mode 100644 source/hid/hid.h create mode 100644 source/hid/joycon.c create mode 100644 source/hid/joycon.h create mode 100644 source/power/regulator_5v.c create mode 100644 source/power/regulator_5v.h diff --git a/source/hid/hid.c b/source/hid/hid.c new file mode 100644 index 0000000..9f8b925 --- /dev/null +++ b/source/hid/hid.c @@ -0,0 +1,66 @@ +#include "hid.h" +#include "joycon.h" +#include "../utils/btn.h" + +static Inputs inputs = {0}; +u16 LbaseX = 0, LbaseY = 0, RbaseX = 0, RbaseY = 0; + +void hidInit(){ + jc_init_hw(); +} + +Inputs *hidRead(){ + jc_gamepad_rpt_t *controller = joycon_poll(); + + inputs.a = controller->a; + inputs.b = controller->b; + inputs.x = controller->x; + inputs.y = controller->y; + inputs.r = controller->r; + inputs.l = controller->l; + inputs.zr = controller->zr; + inputs.zl = controller->zl; + inputs.minus = controller->minus; + inputs.plus = controller->plus; + inputs.home = controller->home; + inputs.cap = controller->cap; + + if (controller->conn_l){ + if (LbaseX == 0 || LbaseY == 0){ + LbaseX = controller->lstick_x; + LbaseY = controller->lstick_y; + } + + inputs.Lup = (controller->up || (controller->lstick_y > LbaseY + 500)) ? 1 : 0; + inputs.Ldown = (controller->down || (controller->lstick_y < LbaseY - 500)) ? 1 : 0; + inputs.Lleft = (controller->left || (controller->lstick_x < LbaseX - 500)) ? 1 : 0; + inputs.Lright = (controller->right || (controller->lstick_x > LbaseX + 500)) ? 1 : 0; + } + + if (controller->conn_r){ + if (RbaseX == 0 || RbaseY == 0){ + RbaseX = controller->rstick_x; + RbaseY = controller->rstick_y; + } + + inputs.Rup = (controller->rstick_y > RbaseY + 500) ? 1 : 0; + inputs.Rdown = (controller->rstick_y < RbaseY - 500) ? 1 : 0; + inputs.Rleft = (controller->rstick_x < RbaseX - 500) ? 1 : 0; + inputs.Rright = (controller->rstick_x > RbaseX + 500) ? 1 : 0; + } + + u8 btn = btn_read(); + inputs.volp = (btn & BTN_VOL_UP) ? 1 : 0; + inputs.volm = (btn & BTN_VOL_DOWN) ? 1 : 0; + inputs.pow = (btn & BTN_POWER) ? 1 : 0; + + return &inputs; +} + +Inputs *hidWaitForButton(u32 mask){ + Inputs *in = hidRead(); + while ((in->buttons & mask) == 0){ + in = hidRead(); + } + return in; +} \ No newline at end of file diff --git a/source/hid/hid.h b/source/hid/hid.h new file mode 100644 index 0000000..c4d0f59 --- /dev/null +++ b/source/hid/hid.h @@ -0,0 +1,50 @@ +#pragma once +#include "../utils/types.h" + +#define BIT(n) (1 << n) +#define KEY_A BIT(3) +#define KEY_B BIT(2) +#define KEY_LUP BIT(18) +#define KEY_LDOWN BIT(17) +#define KEY_RUP BIT(7) +#define KEY_RDOWN BIT(6) + +typedef struct _inputs { + union { + struct { + // Joy-Con (R). + u32 y:1; + u32 x:1; + u32 b:1; + u32 a:1; + u32 r:1; + u32 zr:1; + u32 Rdown:1; + u32 Rup:1; + u32 Rright:1; + u32 Rleft:1; + + // Shared + u32 minus:1; + u32 plus:1; + u32 home:1; + u32 cap:1; + u32 volp:1; + u32 volm:1; + u32 pow:1; + + // Joy-Con (L). + u32 Ldown:1; + u32 Lup:1; + u32 Lright:1; + u32 Lleft:1; + u32 l:1; + u32 zl:1; + }; + u32 buttons; + }; +} Inputs; + +void hidInit(); +Inputs *hidRead(); +Inputs *hidWaitForButton(u32 mask); \ No newline at end of file diff --git a/source/hid/joycon.c b/source/hid/joycon.c new file mode 100644 index 0000000..a5a787e --- /dev/null +++ b/source/hid/joycon.c @@ -0,0 +1,807 @@ +/* + * Joy-Con UART driver for Nintendo Switch + * + * Copyright (c) 2019 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "joycon.h" +#include "../gfx/gfx.h" +#include "../power/max17050.h" +#include "../power/regulator_5v.h" +#include "../soc/bpmp.h" +#include "../soc/clock.h" +#include "../soc/gpio.h" +#include "../soc/pinmux.h" +#include "../soc/uart.h" +#include "../soc/t210.h" +#include "../utils/util.h" + +// For disabling driver when logging is enabled. +// #include "../libs/lvgl/lvgl.h" + +#define JC_WIRED_CMD 0x91 +#define JC_WIRED_HID 0x92 +#define JC_WIRED_INIT_REPLY 0x94 +#define JC_INIT_HANDSHAKE 0xA5 + +#define JC_WIRED_CMD_MAC 0x01 +#define JC_WIRED_CMD_10 0x10 + +#define JC_HID_OUTPUT_RPT 0x01 +#define JC_HID_RUMBLE_RPT 0x10 + +#define JC_HID_INPUT_RPT 0x30 +#define JC_HID_SUBMCD_RPT 0x21 + +#define JC_HID_SUBCMD_HCI_STATE 0x06 +#define HCI_STATE_SLEEP 0x00 +#define HCI_STATE_RECONNECT 0x01 +#define HCI_STATE_PAIR 0x02 +#define HCI_STATE_HOME 0x04 +#define JC_HID_SUBCMD_SPI_READ 0x10 +#define JC_HID_SUBCMD_RUMBLE_CTL 0x48 +#define JC_HID_SUBCMD_SND_RUMBLE 0xFF + +#define JC_BTN_MASK_L 0xFF2900 // 0xFFE900: with charge status. +#define JC_BTN_MASK_R 0x76FF + +#define JC_ID_L 1 +#define JC_ID_R 2 + +enum +{ + JC_BATT_EMTPY = 0, + JC_BATT_CRIT = 2, + JC_BATT_LOW = 4, + JC_BATT_MID = 6, + JC_BATT_FULL = 8 +}; + +static const u8 init_jc[] = { + 0xA1, 0xA2, 0xA3, 0xA4 +}; + +static const u8 init_handshake[] = { + 0x19, 0x01, 0x03, 0x07, 0x00, // Uart header. + JC_INIT_HANDSHAKE, 0x02, // Wired cmd and wired subcmd. + 0x01, 0x7E, 0x00, 0x00, 0x00 // Wired subcmd data. +}; + +static const u8 init_get_info[] = { + 0x19, 0x01, 0x03, 0x07, 0x00, // Uart header. + JC_WIRED_CMD, JC_WIRED_CMD_MAC, // Wired cmd and subcmd. + 0x00, 0x00, 0x00, 0x00, 0x24 // Wired subcmd data. +}; + +static const u8 init_finilize[] = { + 0x19, 0x01, 0x03, 0x07, 0x00, // Uart header. + JC_WIRED_CMD, JC_WIRED_CMD_10, // Wired cmd and subcmd. + 0x00, 0x00, 0x00, 0x00, 0x3D // Wired subcmd data. +}; + +static const u8 nx_pad_status[] = { + 0x19, 0x01, 0x03, 0x08, 0x00, // Uart header. + JC_WIRED_HID, 0x00, // Wired cmd and hid cmd. + 0x01, 0x00, 0x00, 0x69, 0x2D, 0x1F // hid data. +}; + +typedef struct _jc_uart_hdr_t +{ + u8 magic[3]; + u8 total_size_lsb; + u8 total_size_msb; +} jc_uart_hdr_t; + +typedef struct _jc_wired_hdr_t +{ + jc_uart_hdr_t uart_hdr; + u8 cmd; + u8 data[5]; + u8 crc; + u8 payload[]; +} jc_wired_hdr_t; + +typedef struct _jc_hid_out_rpt_t +{ + u8 cmd; + u8 pkt_id; + u8 rumble[8]; + u8 subcmd; + u8 subcmd_data[]; +} jc_hid_out_rpt_t; + +typedef struct _jc_hid_out_spi_read_t +{ + u32 addr; + u8 size; +} jc_hid_out_spi_read_t; + +typedef struct _jc_hid_in_rpt_t +{ + u8 cmd; + u8 pkt_id; + u8 conn_info:4; + u8 batt_info:4; + u8 btn_right; + u8 btn_shared; + u8 btn_left; + u8 stick_h_left; + u8 stick_m_left; + u8 stick_v_left; + u8 stick_h_right; + u8 stick_m_right; + u8 stick_v_right; + u8 vib_decider; + u8 submcd_ack; + u8 subcmd; + u8 subcmd_data[]; +} jc_hid_in_rpt_t; + +typedef struct _jc_hid_in_spi_read_t +{ + u32 addr; + u8 size; + u8 data[]; +} jc_hid_in_spi_read_t; + +typedef struct _jc_hid_in_pair_data_t +{ + u8 magic; + u8 size; + u16 checksum; + u8 mac[6]; + u8 ltk[16]; + u8 pad0[10]; + u8 bt_caps; // bit3: Secure conn supported host, bit5: Paired to TBFC supported host, bit6: iTBFC page supported + u8 pad1; +} jc_hid_in_pair_data_t; + +typedef struct _joycon_ctxt_t +{ + u8 buf[0x100]; //FIXME: If heap is used, dumping breaks. + u8 uart; + u8 type; + u8 mac[6]; + u32 hw_init_done; + u32 last_received_time; + u32 last_status_req_time; + u8 rumble_sent; + u8 connected; +} joycon_ctxt_t; + +static joycon_ctxt_t jc_l; +static joycon_ctxt_t jc_r; + +static bool jc_init_done = false; +static u32 hid_pkt_inc = 0; + +static jc_gamepad_rpt_t jc_gamepad; + +void jc_power_supply(u8 uart, bool enable); + +void joycon_send_raw(u8 uart_port, const u8 *buf, u16 size) +{ + uart_send(uart_port, buf, size); + uart_wait_idle(uart_port, UART_TX_IDLE); +} + +static u16 jc_packet_add_uart_hdr(jc_wired_hdr_t *out, u8 wired_cmd, u8 *data, u16 size) +{ + out->uart_hdr.magic[0] = 0x19; + out->uart_hdr.magic[1] = 0x01; + out->uart_hdr.magic[2] = 0x3; + + out->uart_hdr.total_size_lsb = 7; + out->uart_hdr.total_size_msb = 0; + out->cmd = wired_cmd; + + if (data) + memcpy(out->data, data, size); + + out->crc = 0; // wired crc8ccit can be skipped. + + return sizeof(jc_wired_hdr_t); +} + +static u16 jc_hid_output_rpt_craft(jc_wired_hdr_t *rpt, u8 *payload, u16 size) +{ + u16 pkt_size = jc_packet_add_uart_hdr(rpt, JC_WIRED_HID, NULL, 0); + pkt_size += size; + + rpt->uart_hdr.total_size_lsb += size; + rpt->data[0] = size >> 8; + rpt->data[1] = size & 0xFF; + + if (payload) + memcpy(rpt->payload, payload, size); + + return pkt_size; +} + +void jc_send_hid_output_rpt(u8 uart, u8 *payload, u16 size) +{ + u8 rpt[0x50]; + memset(rpt, 0, sizeof(rpt)); + + u32 rpt_size = jc_hid_output_rpt_craft((jc_wired_hdr_t *)rpt, payload, size); + + joycon_send_raw(uart, rpt, rpt_size); +} + +static u8 jc_hid_pkt_id_incr() +{ + u8 curr_id = hid_pkt_inc; + hid_pkt_inc++; + + return (curr_id & 0xF); +} + +void jc_send_hid_cmd(u8 uart, u8 subcmd, u8 *data, u16 size) +{ + u8 temp[0x30]; + u8 rumble_neutral[8] = {0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40}; + u8 rumble_init[8] = {0xc2, 0xc8, 0x03, 0x72, 0xc2, 0xc8, 0x03, 0x72}; + + memset(temp, 0, sizeof(temp)); + + jc_hid_out_rpt_t *hid_pkt = (jc_hid_out_rpt_t *)temp; + + memcpy(hid_pkt->rumble, rumble_neutral, sizeof(rumble_neutral)); + + if (subcmd == JC_HID_SUBCMD_SND_RUMBLE) + { + bool send_r_rumble = jc_r.connected && !jc_r.rumble_sent; + bool send_l_rumble = jc_l.connected && !jc_l.rumble_sent; + + // Enable rumble. + hid_pkt->cmd = JC_HID_OUTPUT_RPT; + hid_pkt->pkt_id = jc_hid_pkt_id_incr(); + hid_pkt->subcmd = JC_HID_SUBCMD_RUMBLE_CTL; + hid_pkt->subcmd_data[0] = 1; + if (send_r_rumble) + jc_send_hid_output_rpt(UART_B, (u8 *)hid_pkt, 0x10); + if (send_l_rumble) + jc_send_hid_output_rpt(UART_C, (u8 *)hid_pkt, 0x10); + + // Send rumble. + hid_pkt->cmd = JC_HID_RUMBLE_RPT; + hid_pkt->pkt_id = jc_hid_pkt_id_incr(); + memcpy(hid_pkt->rumble, rumble_init, sizeof(rumble_init)); + if (send_r_rumble) + jc_send_hid_output_rpt(UART_B, (u8 *)hid_pkt, 10); + if (send_l_rumble) + jc_send_hid_output_rpt(UART_C, (u8 *)hid_pkt, 10); + + msleep(15); + + // Disable rumble. + hid_pkt->cmd = JC_HID_OUTPUT_RPT; + hid_pkt->pkt_id = jc_hid_pkt_id_incr(); + hid_pkt->subcmd = JC_HID_SUBCMD_RUMBLE_CTL; + hid_pkt->subcmd_data[0] = 0; + memcpy(hid_pkt->rumble, rumble_neutral, sizeof(rumble_neutral)); + if (send_r_rumble) + jc_send_hid_output_rpt(UART_B, (u8 *)hid_pkt, 0x10); + if (send_l_rumble) + jc_send_hid_output_rpt(UART_C, (u8 *)hid_pkt, 0x10); + } + else + { + hid_pkt->cmd = JC_HID_OUTPUT_RPT; + hid_pkt->pkt_id = jc_hid_pkt_id_incr(); + hid_pkt->subcmd = subcmd; + if (data) + memcpy(hid_pkt->subcmd_data, data, size); + + u8 pkt_size = sizeof(jc_hid_out_rpt_t) + size; + + if (subcmd != JC_HID_SUBCMD_SPI_READ) + jc_send_hid_output_rpt(uart, (u8 *)hid_pkt, pkt_size); + else + { + jc_send_hid_output_rpt(UART_B, (u8 *)hid_pkt, pkt_size); + jc_send_hid_output_rpt(UART_C, (u8 *)hid_pkt, pkt_size); + } + } +} + +static void jc_charging_decider(u8 batt, u8 uart) +{ + u32 system_batt_enough = max17050_get_cached_batt_volt() > 4000; + + // Power supply control based on battery levels and charging. + if ((batt >> 1 << 1) < JC_BATT_LOW) // Level without checking charging. + jc_power_supply(uart, true); + else if (batt > (system_batt_enough ? JC_BATT_FULL : JC_BATT_MID)) // Addresses the charging bit. + jc_power_supply(uart, false); +} + +static void jc_parse_wired_hid(joycon_ctxt_t *jc, const u8* packet, u32 size) +{ + u32 btn_tmp; + jc_hid_in_rpt_t *hid_pkt = (jc_hid_in_rpt_t *)packet; + + switch (hid_pkt->cmd) + { + case JC_HID_INPUT_RPT: + btn_tmp = hid_pkt->btn_right | hid_pkt->btn_shared << 8 | hid_pkt->btn_left << 16; + + if (jc->type & JC_ID_L) + { + jc_gamepad.buttons &= ~JC_BTN_MASK_L; + jc_gamepad.buttons |= (btn_tmp & JC_BTN_MASK_L); + + jc_gamepad.lstick_x = hid_pkt->stick_h_left | ((hid_pkt->stick_m_left & 0xF) << 8); + jc_gamepad.lstick_y = (hid_pkt->stick_m_left >> 4) | (hid_pkt->stick_v_left << 4); + + jc_gamepad.batt_info_l = hid_pkt->batt_info; + } + else if (jc->type & JC_ID_R) + { + jc_gamepad.buttons &= ~JC_BTN_MASK_R; + jc_gamepad.buttons |= (btn_tmp & JC_BTN_MASK_R); + + jc_gamepad.rstick_x = hid_pkt->stick_h_right | ((hid_pkt->stick_m_right & 0xF) << 8); + jc_gamepad.rstick_y = (hid_pkt->stick_m_right >> 4) | (hid_pkt->stick_v_right << 4); + + jc_gamepad.batt_info_r = hid_pkt->batt_info; + } + + jc_gamepad.conn_l = jc_l.connected; + jc_gamepad.conn_r = jc_r.connected; + + jc_charging_decider(hid_pkt->batt_info, jc->uart); + break; + case JC_HID_SUBMCD_RPT: + if (hid_pkt->subcmd == JC_HID_SUBCMD_SPI_READ) + { + jc_bt_conn_t *bt_conn; + + if (jc->type & JC_ID_L) + bt_conn = &jc_gamepad.bt_conn_l; + else + bt_conn = &jc_gamepad.bt_conn_r; + + jc_hid_in_spi_read_t *spi_info = (jc_hid_in_spi_read_t *)hid_pkt->subcmd_data; + jc_hid_in_pair_data_t *pair_data = (jc_hid_in_pair_data_t *)spi_info->data; + + // Check if we reply is pairing info. + if (spi_info->addr == 0x2000 && spi_info->size == 0x1A && pair_data->magic == 0x95) + { + bt_conn->type = jc->type; + + memcpy(bt_conn->mac, jc->mac, 6); + memcpy(bt_conn->host_mac, pair_data->mac, 6); + for (u32 i = 16; i > 0; i--) + bt_conn->ltk[16 - i] = pair_data->ltk[i - 1]; + } + } + break; + default: + break; + } + jc->last_received_time = get_tmr_ms(); +} + +static void jc_parse_wired_init(joycon_ctxt_t *jc, const u8* data, u32 size) +{ + switch (data[0]) + { + case JC_WIRED_CMD_MAC: + for (int i = 12; i > 6; i--) + jc->mac[12 - i] = data[i]; + jc->type = data[6]; + jc->connected = true; + default: + break; + } +} + +static void jc_uart_pkt_parse(joycon_ctxt_t *jc, const u8* packet, size_t size) +{ + jc_wired_hdr_t *pkt = (jc_wired_hdr_t *)packet; + switch (pkt->cmd) + { + case JC_WIRED_HID: + jc_parse_wired_hid(jc, pkt->payload, (pkt->data[0] << 8) | pkt->data[1]); + break; + case JC_WIRED_INIT_REPLY: + jc_parse_wired_init(jc, pkt->data, size - sizeof(jc_uart_hdr_t) - 1); + break; + default: + break; + } +} + +static void jc_rcv_pkt(joycon_ctxt_t *jc) +{ + if (gpio_read(GPIO_PORT_E, GPIO_PIN_6) && jc->uart == UART_C) + return; + else if (gpio_read(GPIO_PORT_H, GPIO_PIN_6) && jc->uart == UART_B) + return; + + // Check if device stopped sending data. + u32 uart_irq = uart_get_IIR(jc->uart); + if ((uart_irq & 0x8) != 0x8) + return; + + u32 len = uart_recv(jc->uart, (u8 *)jc->buf, 0); + + // Check valid size and uart reply magic. + if (len > 7 && !memcmp(jc->buf, "\x19\x81\x03", 3)) + { + jc_wired_hdr_t *pkt = (jc_wired_hdr_t *)(jc->buf); + + jc_uart_pkt_parse(jc, jc->buf, pkt->uart_hdr.total_size_lsb + sizeof(jc_uart_hdr_t)); + } +} + +static bool jc_send_init_rumble(joycon_ctxt_t *jc) +{ + // Send init rumble or request nx pad status report. + if ((jc_r.connected && !jc_r.rumble_sent) || (jc_l.connected && !jc_l.rumble_sent)) + { + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_SPIO); + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_SPIO); + + jc_send_hid_cmd(jc->uart, JC_HID_SUBCMD_SND_RUMBLE, NULL, 0); + + if (jc_l.connected) + jc_l.rumble_sent = true; + if (jc_r.connected) + jc_r.rumble_sent = true; + + if (jc->uart != UART_B) + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_GPIO); + else + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_GPIO); + + return 1; + } + + return 0; +} + +static void jc_req_nx_pad_status(joycon_ctxt_t *jc) +{ + bool sent_rumble = jc_send_init_rumble(jc); + + if (sent_rumble) + return; + + if (jc->last_status_req_time > get_tmr_ms() || !jc->connected) + return; + + // Turn off Joy-Con detect. + if (jc->uart == UART_B) + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_SPIO); + else + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_SPIO); + + joycon_send_raw(jc->uart, nx_pad_status, sizeof(nx_pad_status)); + + // Turn Joy-Con detect on. + if (jc->uart == UART_B) + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_GPIO); + else + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_GPIO); + + jc->last_status_req_time = get_tmr_ms() + 15; +} + +jc_gamepad_rpt_t *jc_get_bt_pairing_info() +{ + u8 retries; + jc_bt_conn_t *bt_conn; + + bt_conn = &jc_gamepad.bt_conn_l; + memset(bt_conn->host_mac, 0, 6); + memset(bt_conn->ltk, 0, 16); + + bt_conn = &jc_gamepad.bt_conn_r; + memset(bt_conn->host_mac, 0, 6); + memset(bt_conn->ltk, 0, 16); + + while (jc_l.last_status_req_time > get_tmr_ms()) + { + jc_rcv_pkt(&jc_r); + jc_rcv_pkt(&jc_l); + } + + jc_hid_in_spi_read_t subcmd_data; + subcmd_data.addr = 0x2000; + subcmd_data.size = 0x1A; + + // Turn off Joy-Con detect. + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_SPIO); + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_SPIO); + + bool jc_r_found = jc_r.connected ? false : true; + bool jc_l_found = jc_l.connected ? false : true; + + u32 total_retries = 5; +retry: + retries = 10; + while (retries) + { + if (jc_l.last_status_req_time < get_tmr_ms()) + { + jc_send_hid_cmd(0, JC_HID_SUBCMD_SPI_READ, (u8 *)&subcmd_data, 5); + jc_l.last_status_req_time = get_tmr_ms() + 15; + jc_r.last_status_req_time = get_tmr_ms() + 15; + retries--; + } + + if (!jc_r_found) + { + memset(jc_r.buf, 0, 0x100); + jc_rcv_pkt(&jc_r); + } + + if (!jc_l_found) + { + memset(jc_l.buf, 0, 0x100); + jc_rcv_pkt(&jc_l); + } + } + + if (jc_l.connected && + memcmp(&jc_gamepad.bt_conn_l.ltk[0], "\x00\x00\x00\x00", 4) && + memcmp(&jc_gamepad.bt_conn_l.ltk[8], "\x00\x00\x00\x00", 4)) + jc_l_found = true; + + if (jc_r.connected && + memcmp(&jc_gamepad.bt_conn_r.ltk[0], "\x00\x00\x00\x00", 4) && + memcmp(&jc_gamepad.bt_conn_r.ltk[8], "\x00\x00\x00\x00", 4)) + jc_r_found = true; + + if (total_retries && (!jc_l_found || !jc_r_found)) + { + total_retries--; + goto retry; + } + + // Turn Joy-Con detect on. + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_GPIO); + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_GPIO); + + return &jc_gamepad; +} + +void jc_deinit() +{ + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_SPIO); + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_SPIO); + + u8 data = HCI_STATE_SLEEP; + + if (jc_r.connected) + { + jc_send_hid_cmd(UART_B, JC_HID_SUBCMD_HCI_STATE, &data, 1); + msleep(1); + jc_rcv_pkt(&jc_r); + } + if (jc_l.connected) + { + jc_send_hid_cmd(UART_C, JC_HID_SUBCMD_HCI_STATE, &data, 1); + msleep(1); + jc_rcv_pkt(&jc_l); + } + + jc_power_supply(UART_B, false); + jc_power_supply(UART_C, false); +} + +static void jc_init_conn(joycon_ctxt_t *jc) +{ + if (((u32)get_tmr_ms() - jc->last_received_time) > 1000) + { + jc_power_supply(jc->uart, true); + + // Turn off Joy-Con detect. + if (jc->uart == UART_B) + { + jc_gamepad.buttons &= ~JC_BTN_MASK_R; + jc_gamepad.conn_r = false; + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_SPIO); + } + else + { + jc_gamepad.buttons &= ~JC_BTN_MASK_L; + jc_gamepad.conn_l = false; + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_SPIO); + } + + uart_init(jc->uart, 1000000); + uart_invert(jc->uart, true, UART_INVERT_TXD); + uart_set_IIR(jc->uart); + + joycon_send_raw(jc->uart, init_jc, 4); + joycon_send_raw(jc->uart, init_handshake, sizeof(init_handshake)); + + msleep(5); + jc_rcv_pkt(jc); + + joycon_send_raw(jc->uart, init_get_info, sizeof(init_get_info)); + msleep(5); + jc_rcv_pkt(jc); + + joycon_send_raw(jc->uart, init_finilize, sizeof(init_finilize)); + msleep(5); + jc_rcv_pkt(jc); + + // Turn Joy-Con detect on. + if (jc->uart == UART_B) + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_GPIO); + else + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_GPIO); + + jc->last_received_time = get_tmr_ms(); + + if (jc->connected) + jc_power_supply(jc->uart, false); + } +} + +static void jc_conn_check() +{ + // Check if a Joy-Con was disconnected. + if (gpio_read(GPIO_PORT_E, GPIO_PIN_6)) + { + jc_power_supply(UART_C, false); + + hid_pkt_inc = 0; + + jc_l.connected = false; + jc_l.rumble_sent = false; + + jc_gamepad.buttons &= ~JC_BTN_MASK_L; + jc_gamepad.conn_l = false; + + jc_gamepad.batt_info_l = 0; + jc_gamepad.bt_conn_l.type = 0; + } + + if (gpio_read(GPIO_PORT_H, GPIO_PIN_6)) + { + jc_power_supply(UART_B, false); + + hid_pkt_inc = 0; + + jc_r.connected = false; + jc_r.rumble_sent = false; + + jc_gamepad.buttons &= ~JC_BTN_MASK_R; + jc_gamepad.conn_r = false; + + jc_gamepad.batt_info_r = 0; + jc_gamepad.bt_conn_r.type = 0; + } +} + +void jc_power_supply(u8 uart, bool enable) +{ + if (enable) + { + if (regulator_get_5v_dev_enabled(1 << uart)) + return; + + regulator_enable_5v(1 << uart); + + if (jc_init_done) + { + if (uart == UART_C) + gpio_write(GPIO_PORT_CC, GPIO_PIN_3, GPIO_HIGH); + else + gpio_write(GPIO_PORT_K, GPIO_PIN_3, GPIO_HIGH); + return; + } + + if (uart == UART_C) + { + // Joy-Con(L) Charge Detect. + PINMUX_AUX(PINMUX_AUX_SPDIF_IN) = PINMUX_PULL_DOWN | 1; + gpio_config(GPIO_PORT_CC, GPIO_PIN_3, GPIO_MODE_GPIO); + gpio_output_enable(GPIO_PORT_CC, GPIO_PIN_3, GPIO_OUTPUT_ENABLE); + gpio_write(GPIO_PORT_CC, GPIO_PIN_3, GPIO_HIGH); + } + else + { + // Joy-Con(R) Charge Detect. + PINMUX_AUX(PINMUX_AUX_GPIO_PK3) = PINMUX_DRIVE_4X | PINMUX_PULL_DOWN | 2; + gpio_config(GPIO_PORT_K, GPIO_PIN_3, GPIO_MODE_GPIO); + gpio_output_enable(GPIO_PORT_K, GPIO_PIN_3, GPIO_OUTPUT_ENABLE); + gpio_write(GPIO_PORT_K, GPIO_PIN_3, GPIO_HIGH); + } + } + else + { + if (!regulator_get_5v_dev_enabled(1 << uart)) + return; + + regulator_disable_5v(1 << uart); + + if (uart == UART_C) + gpio_write(GPIO_PORT_CC, GPIO_PIN_3, GPIO_LOW); + else + gpio_write(GPIO_PORT_K, GPIO_PIN_3, GPIO_LOW); + } +} + +void jc_init_hw() +{ + jc_l.uart = UART_C; + jc_r.uart = UART_B; + +#if (LV_LOG_PRINTF != 1) + jc_power_supply(UART_C, true); + jc_power_supply(UART_B, true); + + // Joy-Con (R) IsAttached. + PINMUX_AUX(PINMUX_AUX_GPIO_PH6) = PINMUX_INPUT_ENABLE | PINMUX_TRISTATE; + gpio_config(GPIO_PORT_H, GPIO_PIN_6, GPIO_MODE_GPIO); + + // Joy-Con (L) IsAttached. + PINMUX_AUX(PINMUX_AUX_GPIO_PE6) = PINMUX_INPUT_ENABLE | PINMUX_TRISTATE; + gpio_config(GPIO_PORT_E, GPIO_PIN_6, GPIO_MODE_GPIO); + + // Configure pinmuxing for UART B and C. + pinmux_config_uart(UART_B); + pinmux_config_uart(UART_C); + + // Ease the stress to APB. + bpmp_clk_rate_set(BPMP_CLK_NORMAL); + + // Enable UART B and C clocks. + clock_enable_uart(UART_B); + clock_enable_uart(UART_C); + + // Restore OC. + bpmp_clk_rate_set(BPMP_CLK_DEFAULT_BOOST); + + // Turn Joy-Con detect on. + gpio_config(GPIO_PORT_G, GPIO_PIN_0, GPIO_MODE_GPIO); + gpio_config(GPIO_PORT_D, GPIO_PIN_1, GPIO_MODE_GPIO); + + jc_init_done = true; +#endif +} + +jc_gamepad_rpt_t *joycon_poll() +{ + if (!jc_init_done) + return NULL; + + if (!gpio_read(GPIO_PORT_H, GPIO_PIN_6)) + jc_init_conn(&jc_r); + if (!gpio_read(GPIO_PORT_E, GPIO_PIN_6)) + jc_init_conn(&jc_l); + + if (!gpio_read(GPIO_PORT_H, GPIO_PIN_6)) + jc_req_nx_pad_status(&jc_r); + if (!gpio_read(GPIO_PORT_E, GPIO_PIN_6)) + jc_req_nx_pad_status(&jc_l); + + if (!gpio_read(GPIO_PORT_H, GPIO_PIN_6)) + jc_rcv_pkt(&jc_r); + if (!gpio_read(GPIO_PORT_E, GPIO_PIN_6)) + jc_rcv_pkt(&jc_l); + + jc_conn_check(); + + return &jc_gamepad; +} diff --git a/source/hid/joycon.h b/source/hid/joycon.h new file mode 100644 index 0000000..2d7d8f9 --- /dev/null +++ b/source/hid/joycon.h @@ -0,0 +1,98 @@ +/* + * Ambient light sensor driver for Nintendo Switch's Rohm BH1730 + * + * Copyright (c) 2018 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __JOYCON_H_ +#define __JOYCON_H_ + +#include "../utils/types.h" + +#define JC_BTNS_DIRECTION_PAD 0xF0000 +#define JC_BTNS_PREV_NEXT 0x800080 +#define JC_BTNS_ENTER 0x400008 +#define JC_BTNS_ESC 0x4 + +#define JC_BTNS_ALL (JC_BTNS_PREV_NEXT | JC_BTNS_ENTER | JC_BTNS_DIRECTION_PAD | JC_BTNS_ESC) + +typedef struct _jc_bt_conn_t +{ + u8 type; + u8 mac[6]; + u8 host_mac[6]; + u8 ltk[16]; +} jc_bt_conn_t; + +typedef struct _jc_gamepad_rpt_t +{ + union + { + struct + { + // Joy-Con (R). + u32 y:1; + u32 x:1; + u32 b:1; + u32 a:1; + u32 sr_r:1; + u32 sl_r:1; + u32 r:1; + u32 zr:1; + + // Shared + u32 minus:1; + u32 plus:1; + u32 r3:1; + u32 l3:1; + u32 home:1; + u32 cap:1; + u32 pad:1; + u32 wired:1; + + // Joy-Con (L). + u32 down:1; + u32 up:1; + u32 right:1; + u32 left:1; + u32 sr_l:1; + u32 sl_l:1; + u32 l:1; + u32 zl:1; + }; + u32 buttons; + }; + + u16 lstick_x; + u16 lstick_y; + u16 rstick_x; + u16 rstick_y; + bool center_stick_l; + bool center_stick_r; + bool conn_l; + bool conn_r; + u8 batt_info_l; + u8 batt_info_r; + jc_bt_conn_t bt_conn_l; + jc_bt_conn_t bt_conn_r; +} jc_gamepad_rpt_t; + +void jc_power_supply(u8 uart, bool enable); +void jc_init_hw(); +void jc_deinit(); +jc_gamepad_rpt_t *joycon_poll(); +jc_gamepad_rpt_t *jc_get_bt_pairing_info(); + +#endif \ No newline at end of file diff --git a/source/power/regulator_5v.c b/source/power/regulator_5v.c new file mode 100644 index 0000000..7705490 --- /dev/null +++ b/source/power/regulator_5v.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../soc/gpio.h" +#include "../soc/pinmux.h" +#include "../soc/pmc.h" +#include "../soc/t210.h" +#include "../utils/types.h" + +static u8 reg_5v_dev = 0; + +void regulator_enable_5v(u8 dev) +{ + // The power supply selection from battery or USB is automatic. + if (!reg_5v_dev) + { + // Fan and Rail power from internal 5V regulator (battery). + PINMUX_AUX(PINMUX_AUX_SATA_LED_ACTIVE) = 1; + gpio_config(GPIO_PORT_A, GPIO_PIN_5, GPIO_MODE_GPIO); + gpio_output_enable(GPIO_PORT_A, GPIO_PIN_5, GPIO_OUTPUT_ENABLE); + gpio_write(GPIO_PORT_A, GPIO_PIN_5, GPIO_HIGH); + + // Fan and Rail power from USB 5V VDD. + PINMUX_AUX(PINMUX_AUX_USB_VBUS_EN0) = PINMUX_LPDR | 1; + gpio_config(GPIO_PORT_CC, GPIO_PIN_4, GPIO_MODE_GPIO); + gpio_output_enable(GPIO_PORT_CC, GPIO_PIN_4, GPIO_OUTPUT_ENABLE); + gpio_write(GPIO_PORT_CC, GPIO_PIN_4, GPIO_HIGH); + + // Make sure GPIO power is enabled. + PMC(APBDEV_PMC_NO_IOPOWER) &= ~PMC_NO_IOPOWER_GPIO_IO_EN; + // Override power detect for GPIO AO IO rails. + PMC(APBDEV_PMC_PWR_DET_VAL) &= ~PMC_PWR_DET_GPIO_IO_EN; + } + reg_5v_dev |= dev; +} + +void regulator_disable_5v(u8 dev) +{ + reg_5v_dev &= ~dev; + + if (!reg_5v_dev) + { + // Rail power from internal 5V regulator (battery). + gpio_write(GPIO_PORT_A, GPIO_PIN_5, GPIO_LOW); + gpio_output_enable(GPIO_PORT_A, GPIO_PIN_5, GPIO_OUTPUT_DISABLE); + gpio_config(GPIO_PORT_A, GPIO_PIN_5, GPIO_MODE_SPIO); + PINMUX_AUX(PINMUX_AUX_SATA_LED_ACTIVE) = PINMUX_PARKED | PINMUX_INPUT_ENABLE; + + // Rail power from USB 5V VDD. + gpio_write(GPIO_PORT_CC, GPIO_PIN_4, GPIO_LOW); + gpio_output_enable(GPIO_PORT_CC, GPIO_PIN_4, GPIO_OUTPUT_DISABLE); + gpio_config(GPIO_PORT_CC, GPIO_PIN_4, GPIO_MODE_SPIO); + PINMUX_AUX(PINMUX_AUX_USB_VBUS_EN0) = PINMUX_IO_HV | PINMUX_LPDR | PINMUX_PARKED | PINMUX_INPUT_ENABLE; + + // GPIO AO IO rails. + PMC(APBDEV_PMC_PWR_DET_VAL) |= PMC_PWR_DET_GPIO_IO_EN; + } +} + +bool regulator_get_5v_dev_enabled(u8 dev) +{ + return (reg_5v_dev & dev); +} diff --git a/source/power/regulator_5v.h b/source/power/regulator_5v.h new file mode 100644 index 0000000..7608881 --- /dev/null +++ b/source/power/regulator_5v.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 CTCaer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _REGULATOR_5V_H_ +#define _REGULATOR_5V_H_ + +#include "../utils/types.h" + +enum +{ + REGULATOR_5V_FAN = (1 << 0), + REGULATOR_5V_JC_R = (1 << 1), + REGULATOR_5V_JC_L = (1 << 2), + REGULATOR_5V_ALL = 0xFF +}; + +void regulator_enable_5v(u8 dev); +void regulator_disable_5v(u8 dev); +bool regulator_get_5v_dev_enabled(u8 dev); + +#endif \ No newline at end of file diff --git a/source/tegraexplorer/gfx/menu.c b/source/tegraexplorer/gfx/menu.c index 7bdf29c..3f97aa9 100644 --- a/source/tegraexplorer/gfx/menu.c +++ b/source/tegraexplorer/gfx/menu.c @@ -6,6 +6,7 @@ #include "../../utils/util.h" #include "../../mem/minerva.h" #include "../../soc/gpio.h" +#include "../../hid/hid.h" extern void sd_unmount(); extern bool sd_inited; @@ -60,10 +61,13 @@ void _printentry(menu_entry entry, bool highlighted, bool refresh){ } } +bool disableB = false; int menu_make(menu_entry *entries, int amount, char *toptext){ - int currentpos = 0, res = 0, offset = 0, delay = 300, minscreen = 0, maxscreen = 29, calculatedamount = 0; + int currentpos = 0, offset = 0, delay = 300, minscreen = 0, maxscreen = 29, calculatedamount = 0; u32 scrolltimer, timer; bool refresh = false; + Inputs *input = hidRead(); + input->buttons = 0; gfx_clearscreen(); @@ -85,7 +89,7 @@ int menu_make(menu_entry *entries, int amount, char *toptext){ gfx_printlength(42, toptext); RESETCOLOR; - while (!(res & BTN_POWER)){ + while (!(input->a)){ gfx_con_setpos(0, 47); timer = get_tmr_ms(); refresh = false; @@ -119,47 +123,54 @@ int menu_make(menu_entry *entries, int amount, char *toptext){ gfx_printf("\n%k%K %s %s\n\nTime taken for screen draw: %dms ", COLOR_BLUE, COLOR_DEFAULT, (offset + 30 < amount) ? "v" : " ", (offset > 0) ? "^" : " ", get_tmr_ms() - timer); - while (btn_read() & BTN_POWER); + while ((input = hidRead())->buttons & (KEY_B | KEY_A)); - res = 0; - while (!res){ + input->buttons = 0; + while (!(input->buttons & (KEY_A | KEY_LDOWN | KEY_LUP | KEY_B | KEY_RUP | KEY_RDOWN))){ if (sd_inited && !!gpio_read(GPIO_PORT_Z, GPIO_PIN_1)){ gfx_errDisplay("menu", ERR_SD_EJECTED, 0); sd_unmount(); return -1; } - res = btn_read(); + input = hidRead(); - if (!res) + if (!(input->buttons & (KEY_A | KEY_LDOWN | KEY_LUP | KEY_B | KEY_RUP | KEY_RDOWN))) delay = 300; if (delay < 300){ scrolltimer = get_tmr_ms(); - while (res){ + while (input->buttons & (KEY_A | KEY_LDOWN | KEY_LUP | KEY_B | KEY_RUP | KEY_RDOWN)){ if (scrolltimer + delay <= get_tmr_ms()) break; - res = btn_read(); + input = hidRead(); } } - if (delay > 46 && res) + if (delay > 46 && input->buttons & (KEY_A | KEY_LDOWN | KEY_LUP | KEY_B | KEY_RUP | KEY_RDOWN)) delay -= 45; + + if (input->buttons & (KEY_RUP | KEY_RDOWN)) + delay = 1; } - if (res & BTN_VOL_UP && currentpos >= 1){ + if (input->buttons & (KEY_LUP | KEY_RUP) && currentpos >= 1){ currentpos--; while(entries[currentpos].property & (ISSKIP | ISHIDE) && currentpos >= 1) currentpos--; } - else if (res & BTN_VOL_DOWN && currentpos < amount - 1){ + else if (input->buttons & (KEY_LDOWN | KEY_RDOWN) && currentpos < amount - 1){ currentpos++; while(entries[currentpos].property & (ISSKIP | ISHIDE) && currentpos < amount - 1) currentpos++; } + else if (input->b && !disableB){ + currentpos = 0; + break; + } } minerva_periodic_training(); diff --git a/source/tegraexplorer/mainmenu.c b/source/tegraexplorer/mainmenu.c index 47095b4..3ff7a83 100644 --- a/source/tegraexplorer/mainmenu.c +++ b/source/tegraexplorer/mainmenu.c @@ -18,6 +18,8 @@ #include "emmc/emmcoperations.h" #include "emmc/emmcmenu.h" #include "../storage/nx_sd.h" +//#include "../hid/joycon.h" +#include "../hid/hid.h" /* extern bool sd_mount(); extern void sd_unmount(); @@ -25,6 +27,7 @@ extern void sd_unmount(); extern int launch_payload(char *path); extern bool sd_inited; extern bool sd_mounted; +extern bool disableB; int res = 0, meter = 0; @@ -102,8 +105,6 @@ void MainMenu_Credits(){ if (++meter >= 3) gfx_errDisplay("credits", 53, 0); gfx_message(COLOR_WHITE, mainmenu_credits); - int frii = 10/0; - gfx_printf("%d", frii); } void MainMenu_Exit(){ @@ -172,6 +173,8 @@ void te_main(){ disconnect_mmc(); + hidInit(); + while (1){ //fillmainmenu(); @@ -187,7 +190,10 @@ void te_main(){ setter = sd_inited; SETBIT(mainmenu_main[5].property, ISHIDE, !setter); + disableB = true; res = menu_make(mainmenu_main, 8, "-- Main Menu --") + 1; + disableB = false; + RunMenuOption(res); } } \ No newline at end of file