Introducing Bootloader Development Kit (BDK)

BDK will allow developers to use the full collection of drivers,
with limited editing, if any, for making payloads for Nintendo Switch.

Using a single source for everything will also help decoupling
Switch specific code and easily port it to other Tegra X1/X1+ platforms.
And maybe even to lower targets.

Everything is now centrilized into bdk folder.
Every module or project can utilize it by simply including it.

This is just the start and it will continue to improve.
This commit is contained in:
CTCaer
2020-06-14 15:25:21 +03:00
parent 9b1c61fbcf
commit 185526d134
386 changed files with 0 additions and 31374 deletions

117
bdk/input/als.c Normal file
View File

@@ -0,0 +1,117 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "als.h"
#include "../power/max77620.h"
#include "../soc/clock.h"
#include "../soc/i2c.h"
#include "../soc/pinmux.h"
#include "../utils/util.h"
#define HOS_GAIN BH1730_GAIN_64X
#define HOS_ITIME 38
void set_als_cfg(als_table_t *als_val, u8 gain, u8 itime)
{
i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_GAIN_REG), gain);
i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_TIMING_REG), (256 - itime));
als_val->gain = gain;
als_val->itime = itime;
}
void get_als_lux(als_table_t *als_val)
{
u32 data[2];
float pre_gain_lux;
float visible_light;
float ir_light;
float light_ratio;
u8 adc_ready = 0;
u8 retries = 100;
const float als_gain_idx_tbl[4] = { 1.0, 2.0, 64.0, 128.0 };
const float als_norm_res = 100.0;
const float als_multiplier = 3.6;
const float als_tint = 2.7;
// Wait for ADC to prepare new data.
while (!(adc_ready & BH1730_CTL_ADC_VALID) && retries)
{
retries--;
adc_ready = i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_CONTROL_REG));
}
// Get visible and ir light raw data.
data[0] = i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_DATA0LOW_REG)) +
(i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_DATA0HIGH_REG)) << 8);
data[1] = i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_DATA1LOW_REG)) +
(i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_DATA1HIGH_REG)) << 8);
als_val->over_limit = data[0] > 65534 || data[1] > 65534;
als_val->vi_light = data[0];
als_val->ir_light = data[1];
if (!data[0] || !retries)
{
als_val->lux = 0.0;
return;
}
visible_light = (float)data[0];
ir_light = (float)data[1];
light_ratio = (float)data[1] / (float)data[0];
// The following are specific to the light filter Switch uses.
if (light_ratio < 0.5)
pre_gain_lux = visible_light * 5.002 - ir_light * 7.502;
else if (light_ratio < 0.754)
pre_gain_lux = visible_light * 2.250 - ir_light * 2.000;
else if (light_ratio < 1.029)
pre_gain_lux = visible_light * 1.999 - ir_light * 1.667;
else if (light_ratio < 1.373)
pre_gain_lux = visible_light * 0.884 - ir_light * 0.583;
else if (light_ratio < 1.879)
pre_gain_lux = visible_light * 0.309 - ir_light * 0.165;
else pre_gain_lux = 0.0;
als_val->lux = (pre_gain_lux / als_gain_idx_tbl[als_val->gain]) * (als_norm_res / ((float)als_val->itime * als_tint)) * als_multiplier;
}
u8 als_init(als_table_t *als_val)
{
pinmux_config_i2c(I2C_2);
clock_enable_i2c(I2C_2);
i2c_init(I2C_2);
max77620_regulator_set_volt_and_flags(REGULATOR_LDO6, 2900000, MAX77620_POWER_MODE_NORMAL);
i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_LDO6_CFG2, 0xD8 | MAX77620_LDO_CFG2_ADE_MASK);
u8 id = i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(0x12));
i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_SPEC(BH1730_SPECCMD_RESET), 0);
i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_GAIN_REG), HOS_GAIN);
i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_TIMING_REG), (256 - HOS_ITIME));
i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_CONTROL_REG), BH1730_CTL_POWER_ON | BH1730_CTL_ADC_EN);
als_val->gain = HOS_GAIN;
als_val->itime = HOS_ITIME;
return id;
}

65
bdk/input/als.h Normal file
View File

@@ -0,0 +1,65 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __ALS_H_
#define __ALS_H_
#include "../utils/types.h"
#define BH1730_I2C_ADDR 0x29
#define BH1730_CMD_MAGIC 0x80
#define BH1730_CMD_SETADDR 0x00
#define BH1730_CMD_SPECCMD 0x60
#define BH1730_SPECCMD_RESET 0x4
#define BH1730_CONTROL_REG 0x00
#define BH1730_CTL_ADC_VALID 0x10
#define BH1730_CTL_ONE_TIME 0x08
#define BH1730_CTL_DAT0_ONLY 0x04
#define BH1730_CTL_ADC_EN 0x02
#define BH1730_CTL_POWER_ON 0x01
#define BH1730_TIMING_REG 0x01
#define BH1730_GAIN_REG 0x07
#define BH1730_GAIN_1X 0x00
#define BH1730_GAIN_2X 0x01
#define BH1730_GAIN_64X 0x02
#define BH1730_GAIN_128X 0x03
#define BH1730_DATA0LOW_REG 0x14
#define BH1730_DATA0HIGH_REG 0x15
#define BH1730_DATA1LOW_REG 0x16
#define BH1730_DATA1HIGH_REG 0x17
#define BH1730_ADDR(reg) (BH1730_CMD_MAGIC | BH1730_CMD_SETADDR | reg)
#define BH1730_SPEC(cmd) (BH1730_CMD_MAGIC | BH1730_CMD_SPECCMD | cmd)
typedef struct _als_table_t
{
float lux;
bool over_limit;
u32 vi_light;
u32 ir_light;
u8 gain;
u8 itime;
} als_table_t;
void set_als_cfg(als_table_t *als_val, u8 gain, u8 itime);
void get_als_lux(als_table_t *als_val);
u8 als_init(als_table_t *als_val);
#endif /* __ALS_H_ */

872
bdk/input/joycon.c Normal file
View File

@@ -0,0 +1,872 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#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 SPI_READ_OFFSET 0x20
#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;
jc_send_hid_output_rpt(uart, (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->size == 0x1A && pair_data->magic == 0x95 && pair_data->size == 0x22)
{
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, 0x100);
// 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;
}
static bool _jc_validate_pairing_info(u8 *buf, bool *is_hos)
{
u8 crc = 0;
for (u32 i = 0; i < 0x22; i++)
crc += buf[4 + i];
crc += 0x68; // Host is Switch.
if ((crc ^ 0x55) == buf[2])
*is_hos = true;
crc -= 0x68;
crc += 0x08; // Host is PC.
if (*is_hos || (crc ^ 0x55) == buf[2])
return true;
return false;
}
jc_gamepad_rpt_t *jc_get_bt_pairing_info(bool *is_l_hos, bool *is_r_hos)
{
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_l;
subcmd_data_l.addr = 0x2000;
subcmd_data_l.size = 0x1A;
jc_hid_in_spi_read_t subcmd_data_r;
subcmd_data_r.addr = 0x2000;
subcmd_data_r.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 = 10;
retry:
retries = 10;
while (retries)
{
u32 time_now = get_tmr_ms();
if ((!jc_l_found && jc_l.last_status_req_time < time_now) || (!jc_r_found && jc_r.last_status_req_time < time_now))
{
if (!jc_l_found)
{
jc_send_hid_cmd(jc_l.uart, JC_HID_SUBCMD_SPI_READ, (u8 *)&subcmd_data_l, 5);
jc_l.last_status_req_time = get_tmr_ms() + 15;
}
if (!jc_r_found)
{
jc_send_hid_cmd(jc_r.uart, JC_HID_SUBCMD_SPI_READ, (u8 *)&subcmd_data_r, 5);
jc_r.last_status_req_time = get_tmr_ms() + 15;
}
retries--;
}
if (!jc_l_found)
{
memset(jc_l.buf, 0, 0x100);
jc_rcv_pkt(&jc_l);
bool is_hos = false;
if (_jc_validate_pairing_info(&jc_l.buf[SPI_READ_OFFSET], &is_hos))
{
bool is_active = jc_l.buf[SPI_READ_OFFSET] == 0x95;
if (!is_active)
subcmd_data_l.addr += 0x26; // Get next slot.
else
jc_l_found = true; // Entry is active.
if (jc_l_found && is_hos)
*is_l_hos = true;
}
}
if (!jc_r_found)
{
memset(jc_r.buf, 0, 0x100);
jc_rcv_pkt(&jc_r);
bool is_hos = false;
if (_jc_validate_pairing_info(&jc_r.buf[SPI_READ_OFFSET], &is_hos))
{
bool is_active = jc_r.buf[SPI_READ_OFFSET] == 0x95;
if (!is_active)
subcmd_data_r.addr += 0x26; // Get next slot.
else
jc_r_found = true; // Entry is active.
if (jc_r_found && is_hos)
*is_r_hos = true;
}
}
if (jc_l_found && jc_r_found)
break;
}
if (!jc_l_found || !jc_r_found)
{
if (total_retries)
{
total_retries--;
goto retry;
}
if (!jc_l_found)
{
bt_conn = &jc_gamepad.bt_conn_l;
memset(bt_conn->host_mac, 0, 6);
memset(bt_conn->ltk, 0, 16);
}
if (!jc_r_found)
{
bt_conn = &jc_gamepad.bt_conn_r;
memset(bt_conn->host_mac, 0, 6);
memset(bt_conn->ltk, 0, 16);
}
}
// 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);
jc_rcv_pkt(&jc_r);
}
if (jc_l.connected)
{
jc_send_hid_cmd(UART_C, JC_HID_SUBCMD_HCI_STATE, &data, 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;
}

98
bdk/input/joycon.h Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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(bool *is_l_hos, bool *is_r_hos);
#endif

422
bdk/input/touch.c Normal file
View File

@@ -0,0 +1,422 @@
/*
* Touch driver for Nintendo Switch's STM FingerTip S (4CD60D) touch controller
*
* Copyright (c) 2018 langerhans
* Copyright (c) 2018-2020 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 <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "../soc/clock.h"
#include "../soc/i2c.h"
#include "../soc/pinmux.h"
#include "../power/max7762x.h"
#include "../power/max77620.h"
#include "../soc/gpio.h"
#include "../soc/t210.h"
#include "../utils/btn.h"
#include "../utils/util.h"
#include "touch.h"
#include "../gfx/gfx.h"
#define DPRINTF(...) gfx_printf(__VA_ARGS__)
static int touch_command(u8 cmd, u8 *buf, u8 size)
{
int res = i2c_send_buf_small(I2C_3, STMFTS_I2C_ADDR, cmd, buf, size);
if (!res)
return 1;
return 0;
}
static int touch_read_reg(u8 *cmd, u32 csize, u8 *buf, u32 size)
{
int res = i2c_send_buf_small(I2C_3, STMFTS_I2C_ADDR, cmd[0], &cmd[1], csize - 1);
if (res)
res = i2c_recv_buf(buf, size, I2C_3, STMFTS_I2C_ADDR);
if (!res)
return 1;
return 0;
}
static int touch_wait_event(u8 event, u8 status, u32 timeout)
{
u32 timer = get_tmr_ms() + timeout;
while (true)
{
u8 tmp[8] = {0};
i2c_recv_buf_small(tmp, 8, I2C_3, STMFTS_I2C_ADDR, STMFTS_READ_ONE_EVENT);
if (tmp[1] == event && tmp[2] == status)
return 0;
if (get_tmr_ms() > timer)
return 1;
}
}
#define X_REAL_MAX 1264
#define Y_REAL_MAX 704
#define EDGE_OFFSET 15
static void _touch_compensate_limits(touch_event *event, bool touching)
{
event->x = MAX(event->x, EDGE_OFFSET);
event->x = MIN(event->x, X_REAL_MAX);
event->x -= EDGE_OFFSET;
u32 x_adj = (1280 * 1000) / (X_REAL_MAX - EDGE_OFFSET);
event->x = ((u32)event->x * x_adj) / 1000;
if (touching)
{
event->y = MAX(event->y, EDGE_OFFSET);
event->y = MIN(event->y, Y_REAL_MAX);
event->y -= EDGE_OFFSET;
u32 y_adj = (720 * 1000) / (Y_REAL_MAX - EDGE_OFFSET);
event->y = ((u32)event->y * y_adj) / 1000;
}
}
static void _touch_process_contact_event(touch_event *event, bool touching)
{
event->x = (event->raw[2] << 4) | ((event->raw[4] & STMFTS_MASK_Y_LSB) >> 4);
// Normally, GUI elements have bigger horizontal estate.
// Avoid parsing y axis when finger is removed to minimize touch noise.
if (touching)
{
event->y = (event->raw[3] << 4) | (event->raw[4] & STMFTS_MASK_X_MSB);
event->z = event->raw[5] | (event->raw[6] << 8);
event->z = event->z << 6;
u16 tmp = 0x40;
if ((event->raw[7] & 0x3F) != 1 && (event->raw[7] & 0x3F) != 0x3F)
tmp = event->raw[7] & 0x3F;
event->z /= tmp + 0x40;
event->fingers = ((event->raw[1] & STMFTS_MASK_TOUCH_ID) >> 4) + 1;
}
else
event->fingers = 0;
_touch_compensate_limits(event, touching);
}
static void _touch_parse_event(touch_event *event)
{
event->type = event->raw[1] & STMFTS_MASK_EVENT_ID;
switch (event->type)
{
case STMFTS_EV_MULTI_TOUCH_ENTER:
case STMFTS_EV_MULTI_TOUCH_MOTION:
_touch_process_contact_event(event, true);
if (event->z < 255) // Reject palm rest.
event->touch = true;
else
{
event->touch = false;
event->type = STMFTS_EV_MULTI_TOUCH_LEAVE;
}
break;
case STMFTS_EV_MULTI_TOUCH_LEAVE:
event->touch = false;
_touch_process_contact_event(event, false);
break;
case STMFTS_EV_NO_EVENT:
if (event->touch)
event->type = STMFTS_EV_MULTI_TOUCH_MOTION;
break;
default:
if (event->touch && event->raw[0] == STMFTS_EV_MULTI_TOUCH_MOTION)
event->type = STMFTS_EV_MULTI_TOUCH_MOTION;
else
event->type = STMFTS_EV_MULTI_TOUCH_LEAVE;
}
// gfx_con_setpos(&gfx_con, 0, 300);
// DPRINTF("x = %d \ny = %d \nz = %d \n", event->x, event->y, event->z);
// DPRINTF("0 = %02X\n1 = %02x\n2 = %02x\n3 = %02x\n", event->raw[0], event->raw[1], event->raw[2], event->raw[3]);
// DPRINTF("4 = %02X\n5 = %02x\n6 = %02x\n7 = %02x\n", event->raw[4], event->raw[5], event->raw[6], event->raw[7]);
}
void touch_poll(touch_event *event)
{
i2c_recv_buf_small(event->raw, 8, I2C_3, STMFTS_I2C_ADDR, STMFTS_LATEST_EVENT);
_touch_parse_event(event);
}
touch_event touch_poll_wait()
{
touch_event event;
do
{
touch_poll(&event);
} while (event.type != STMFTS_EV_MULTI_TOUCH_LEAVE);
return event;
}
touch_info touch_get_info()
{
touch_info info;
u8 buf[8];
memset(&buf, 0, 8);
i2c_recv_buf_small(buf, 8, I2C_3, STMFTS_I2C_ADDR, STMFTS_READ_INFO);
info.chip_id = buf[0] << 8 | buf[1];
info.fw_ver = buf[2] << 8 | buf[3];
info.config_id = buf[4];
info.config_ver = buf[5];
//DPRINTF("ID: %04X, FW Ver: %d.%02d\nCfg ID: %02x, Cfg Ver: %d\n",
// info.chip_id, info.fw_ver >> 8, info.fw_ver & 0xFF, info.config_id, info.config_ver);
return info;
}
int touch_get_fw_info(touch_fw_info_t *fw)
{
u8 buf[8] = {0};
// Get fw address info.
u8 cmd[3] = { STMFTS_RW_FRAMEBUFFER_REG, 0, 0x60 };
int res = touch_read_reg(cmd, 3, buf, 3);
if (!res)
{
// Get fw info.
cmd[1] = buf[2]; cmd[2] = buf[1];
res = touch_read_reg(cmd, 3, buf, 8);
if (!res)
{
fw->fw_id = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4];
fw->ftb_ver = (buf[6] << 8) | buf[5];
}
cmd[2]++;
res = touch_read_reg(cmd, 3, buf, 8);
if (!res)
fw->fw_rev = (buf[7] << 8) | buf[6];
}
return res;
}
int touch_sys_reset()
{
u8 cmd[3] = { 0, 0x28, 0x80 }; // System reset cmd.
for (u8 retries = 0; retries < 3; retries++)
{
if (touch_command(STMFTS_WRITE_REG, cmd, 3))
{
msleep(10);
continue;
}
msleep(10);
if (touch_wait_event(STMFTS_EV_CONTROLLER_READY, 0, 20))
continue;
else
return 0;
}
return 1;
}
int touch_panel_ito_test(u8 *err)
{
int res = 0;
// Reset touchscreen module.
if (touch_sys_reset())
return res;
// Do ITO Production test.
u8 cmd[2] = { 1, 0 };
if (touch_command(STMFTS_ITO_CHECK, cmd, 2))
return res;
u32 timer = get_tmr_ms() + 2000;
while (true)
{
u8 tmp[8] = {0};
i2c_recv_buf_small(tmp, 8, I2C_3, STMFTS_I2C_ADDR, STMFTS_READ_ONE_EVENT);
if (tmp[1] == 0xF && tmp[2] == 0x5)
{
if (err)
{
err[0] = tmp[3];
err[1] = tmp[4];
}
res = 1;
break;
}
if (get_tmr_ms() > timer)
break;
}
// Reset touchscreen module.
touch_sys_reset();
return res;
}
int touch_get_fb_info(u8 *buf)
{
u8 tmp[5];
u8 cmd[3] = { STMFTS_RW_FRAMEBUFFER_REG, 0, 0 };
int res = 0;
for (u32 i = 0; i < 0x10000; i+=4)
{
if (!res)
{
cmd[1] = (i >> 8) & 0xFF;
cmd[2] = i & 0xFF;
memset(tmp, 0xCC, 5);
res = touch_read_reg(cmd, 3, tmp, 5);
memcpy(&buf[i], tmp + 1, 4);
}
}
return res;
}
int touch_sense_enable()
{
// Enable auto tuning calibration and multi-touch sensing.
u8 cmd = 1;
if (touch_command(STMFTS_AUTO_CALIBRATION, &cmd, 1))
return 0;
if (touch_command(STMFTS_MS_MT_SENSE_ON, NULL, 0))
return 0;
if (touch_command(STMFTS_CLEAR_EVENT_STACK, NULL, 0))
return 0;
return 1;
}
int touch_execute_autotune()
{
// Reset touchscreen module.
if (touch_sys_reset())
return 0;
// Trim low power oscillator.
if (touch_command(STMFTS_LP_TIMER_CALIB, NULL, 0))
return 0;
msleep(200);
// Apply Mutual Sense Compensation tuning.
if (touch_command(STMFTS_MS_CX_TUNING, NULL, 0))
return 0;
if (touch_wait_event(STMFTS_EV_STATUS, STMFTS_EV_STATUS_MS_CX_TUNING_DONE, 2000))
return 0;
// Apply Self Sense Compensation tuning.
if (touch_command(STMFTS_SS_CX_TUNING, NULL, 0))
return 0;
if (touch_wait_event(STMFTS_EV_STATUS, STMFTS_EV_STATUS_SS_CX_TUNING_DONE, 2000))
return 0;
// Save Compensation data to EEPROM.
if (touch_command(STMFTS_SAVE_CX_TUNING, NULL, 0))
return 0;
if (touch_wait_event(STMFTS_EV_STATUS, STMFTS_EV_STATUS_WRITE_CX_TUNE_DONE, 2000))
return 0;
return touch_sense_enable();
}
static int touch_init()
{
// Initialize touchscreen module.
if (touch_sys_reset())
return 0;
return touch_sense_enable();
}
int touch_power_on()
{
// Enables LDO6 for touchscreen VDD/AVDD supply
max77620_regulator_set_volt_and_flags(REGULATOR_LDO6, 2900000, MAX77620_POWER_MODE_NORMAL);
i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_LDO6_CFG2,
MAX77620_LDO_CFG2_ADE_ENABLE | (3 << 3) | (MAX77620_POWER_MODE_NORMAL << MAX77620_LDO_POWER_MODE_SHIFT));
// Configure touchscreen GPIO.
PINMUX_AUX(PINMUX_AUX_DAP4_SCLK) = PINMUX_PULL_DOWN | 1;
gpio_config(GPIO_PORT_J, GPIO_PIN_7, GPIO_MODE_GPIO);
gpio_output_enable(GPIO_PORT_J, GPIO_PIN_7, GPIO_OUTPUT_ENABLE);
gpio_write(GPIO_PORT_J, GPIO_PIN_7, GPIO_HIGH);
// IRQ and more.
// PINMUX_AUX(PINMUX_AUX_TOUCH_INT) = PINMUX_INPUT_ENABLE | PINMUX_TRISTATE | PINMUX_PULL_UP | 3;
// gpio_config(GPIO_PORT_X, GPIO_PIN_1, GPIO_MODE_GPIO);
// gpio_write(GPIO_PORT_X, GPIO_PIN_1, GPIO_LOW);
// Configure Touscreen and GCAsic shared GPIO.
PINMUX_AUX(PINMUX_AUX_CAM_I2C_SDA) = PINMUX_LPDR | PINMUX_INPUT_ENABLE | PINMUX_TRISTATE | PINMUX_PULL_UP | 2;
PINMUX_AUX(PINMUX_AUX_CAM_I2C_SCL) = PINMUX_IO_HV | PINMUX_LPDR | PINMUX_TRISTATE | PINMUX_PULL_DOWN | 2;
gpio_config(GPIO_PORT_S, GPIO_PIN_3, GPIO_MODE_GPIO);
// Initialize I2C3.
pinmux_config_i2c(I2C_3);
clock_enable_i2c(I2C_3);
i2c_init(I2C_3);
// Wait for the touchscreen module to get ready.
touch_wait_event(STMFTS_EV_CONTROLLER_READY, 0, 20);
// Check for forced boot time calibration.
if (btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN))
{
u8 err[2];
if (touch_panel_ito_test(err))
if (!err[0] && !err[1])
return touch_execute_autotune();
}
// Initialize touchscreen.
u32 retries = 3;
while (retries)
{
if (touch_init())
return 1;
retries--;
}
return 0;
}
void touch_power_off()
{
// Disable touchscreen power.
gpio_write(GPIO_PORT_J, GPIO_PIN_7, GPIO_LOW);
// Disables LDO6 for touchscreen VDD, AVDD supply
max77620_regulator_enable(REGULATOR_LDO6, 0);
i2c_send_byte(I2C_5, MAX77620_I2C_ADDR, MAX77620_REG_LDO6_CFG2,
MAX77620_LDO_CFG2_ADE_ENABLE | (2 << 3) | (MAX77620_POWER_MODE_NORMAL << MAX77620_LDO_POWER_MODE_SHIFT));
clock_disable_i2c(I2C_3);
}

157
bdk/input/touch.h Normal file
View File

@@ -0,0 +1,157 @@
/*
* Touch driver for Nintendo Switch's STM FingerTip S (4CD60D) touch controller
*
* Copyright (c) 2018 langerhans
* Copyright (c) 2018-2020 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __TOUCH_H_
#define __TOUCH_H_
#include "../utils/types.h"
#define STMFTS_I2C_ADDR 0x49
/* I2C commands */
#define STMFTS_READ_INFO 0x80
#define STMFTS_READ_STATUS 0x84
#define STMFTS_READ_ONE_EVENT 0x85
#define STMFTS_READ_ALL_EVENT 0x86
#define STMFTS_LATEST_EVENT 0x87
#define STMFTS_SLEEP_IN 0x90
#define STMFTS_SLEEP_OUT 0x91
#define STMFTS_MS_MT_SENSE_OFF 0x92
#define STMFTS_MS_MT_SENSE_ON 0x93
#define STMFTS_SS_HOVER_SENSE_OFF 0x94
#define STMFTS_SS_HOVER_SENSE_ON 0x95
#define STMFTS_LP_TIMER_CALIB 0x97
#define STMFTS_MS_KEY_SENSE_OFF 0x9A
#define STMFTS_MS_KEY_SENSE_ON 0x9B
#define STMFTS_SYSTEM_RESET 0xA0
#define STMFTS_CLEAR_EVENT_STACK 0xA1
#define STMFTS_FULL_FORCE_CALIBRATION 0xA2
#define STMFTS_MS_CX_TUNING 0xA3
#define STMFTS_SS_CX_TUNING 0xA4
#define STMFTS_ITO_CHECK 0xA7
#define STMFTS_RELEASEINFO 0xAA
#define STMFTS_WRITE_REG 0xB6
#define STMFTS_AUTO_CALIBRATION 0xC3
#define STMFTS_NOISE_WRITE 0xC7
#define STMFTS_NOISE_READ 0xC8
#define STMFTS_RW_FRAMEBUFFER_REG 0xD0
#define STMFTS_SAVE_CX_TUNING 0xFC
#define STMFTS_UNK0 0xB8 //Request compensation
#define STMFTS_UNK1 0xCF
#define STMFTS_UNK2 0xF7
#define STMFTS_UNK3 0xFA
#define STMFTS_UNK4 0xF9
#define STMFTS_UNK5 0x62
/* events */
#define STMFTS_EV_NO_EVENT 0x00
#define STMFTS_EV_MULTI_TOUCH_DETECTED 0x02
#define STMFTS_EV_MULTI_TOUCH_ENTER 0x03
#define STMFTS_EV_MULTI_TOUCH_LEAVE 0x04
#define STMFTS_EV_MULTI_TOUCH_MOTION 0x05
#define STMFTS_EV_HOVER_ENTER 0x07
#define STMFTS_EV_HOVER_LEAVE 0x08
#define STMFTS_EV_HOVER_MOTION 0x09
#define STMFTS_EV_KEY_STATUS 0x0e
#define STMFTS_EV_ERROR 0x0f
#define STMFTS_EV_NOISE_READ 0x17
#define STMFTS_EV_NOISE_WRITE 0x18
#define STMFTS_EV_CONTROLLER_READY 0x10
#define STMFTS_EV_STATUS 0x16
#define STMFTS_EV_DEBUG 0xDB
#define STMFTS_EV_STATUS_MS_CX_TUNING_DONE 1
#define STMFTS_EV_STATUS_SS_CX_TUNING_DONE 2
#define STMFTS_EV_STATUS_WRITE_CX_TUNE_DONE 4
/* multi touch related event masks */
#define STMFTS_MASK_EVENT_ID 0x0F
#define STMFTS_MASK_TOUCH_ID 0xF0
#define STMFTS_MASK_LEFT_EVENT 0x0F
#define STMFTS_MASK_X_MSB 0x0F
#define STMFTS_MASK_Y_LSB 0xF0
/* key related event masks */
#define STMFTS_MASK_KEY_NO_TOUCH 0x00
#define STMFTS_MASK_KEY_MENU 0x01
#define STMFTS_MASK_KEY_BACK 0x02
#define STMFTS_EVENT_SIZE 8
#define STMFTS_STACK_DEPTH 32
#define STMFTS_DATA_MAX_SIZE (STMFTS_EVENT_SIZE * STMFTS_STACK_DEPTH)
#define STMFTS_MAX_FINGERS 10
typedef enum _touch_ito_error {
ITO_NO_ERROR = 0,
ITO_FORCE_OPEN,
ITO_SENSE_OPEN,
ITO_FORCE_SHRT_GND,
ITO_SENSE_SHRT_GND,
ITO_FORCE_SHRT_VCM,
ITO_SENSE_SHRT_VCM,
ITO_FORCE_SHRT_FORCE,
ITO_SENSE_SHRT_SENSE,
ITO_F2E_SENSE,
ITO_FPC_FORCE_OPEN,
ITO_FPC_SENSE_OPEN,
ITO_KEY_FORCE_OPEN,
ITO_KEY_SENSE_OPEN,
ITO_RESERVED0,
ITO_RESERVED1,
ITO_RESERVED2,
ITO_MAX_ERR_REACHED = 0xFF
} touch_ito_error;
typedef struct _touch_event {
u8 raw[8];
u16 type; // Event type.
u16 x; // Horizontal coordinates.
u16 y; // Vertical coordinates.
u32 z;
u8 fingers;
bool touch;
} touch_event;
typedef struct _touch_info {
u16 chip_id;
u16 fw_ver;
u16 config_id;
u16 config_ver;
} touch_info;
typedef struct _touch_fw_info_t {
u32 fw_id;
u16 ftb_ver;
u16 fw_rev;
} touch_fw_info_t;
void touch_poll(touch_event *event);
touch_event touch_poll_wait();
int touch_get_fw_info(touch_fw_info_t *fw);
touch_info touch_get_info();
int touch_panel_ito_test(u8 *err);
int touch_execute_autotune();
int touch_sense_enable();
int touch_power_on();
void touch_power_off();
#endif /* __TOUCH_H_ */