diff --git a/ipl/bq24193.c b/ipl/bq24193.c
new file mode 100644
index 0000000..e4b78fb
--- /dev/null
+++ b/ipl/bq24193.c
@@ -0,0 +1,180 @@
+/*
+* Battery charger driver for Nintendo Switch's TI BQ24193
+*
+* 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 .
+*/
+
+#include "bq24193.h"
+#include "i2c.h"
+#include "util.h"
+
+int bq24193_get_property(enum BQ24193_reg_prop prop, int *value)
+{
+ u8 data;
+
+ switch (prop) {
+ case BQ24193_InputVoltageLimit: // Input voltage limit (mV).
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_InputSource);
+ data = (data & BQ24193_INCONFIG_VINDPM_MASK) >> 3;
+ *value += ((data >> 0) & 1) ? 80 : 0;
+ *value += ((data >> 1) & 1) ? 160 : 0;
+ *value += ((data >> 2) & 1) ? 320 : 0;
+ *value += ((data >> 3) & 1) ? 640 : 0;
+ *value += 3880;
+ break;
+ case BQ24193_IputCurrentLimit: // Input current limit (mA).
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_InputSource);
+ data &= BQ24193_INCONFIG_INLIMIT_MASK;
+ switch (data)
+ {
+ case 0:
+ *value = 100;
+ break;
+ case 1:
+ *value = 150;
+ break;
+ case 2:
+ *value = 500;
+ break;
+ case 3:
+ *value = 900;
+ break;
+ case 4:
+ *value = 1200;
+ break;
+ case 5:
+ *value = 1500;
+ break;
+ case 6:
+ *value = 2000;
+ break;
+ case 7:
+ *value = 3000;
+ break;
+ }
+ break;
+ case BQ24193_SystemMinimumVoltage: // Minimum system voltage limit (mV).
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_PORConfig);
+ *value = (data & BQ24193_PORCONFIG_SYSMIN_MASK) >> 1;
+ *value *= 100;
+ *value += 3000;
+ break;
+ case BQ24193_FastChargeCurrentLimit: // Fast charge current limit (mA).
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_ChrgCurr);
+ data = (data & BQ24193_CHRGCURR_ICHG_MASK) >> 2;
+ *value += ((data >> 0) & 1) ? 64 : 0;
+ *value += ((data >> 1) & 1) ? 128 : 0;
+ *value += ((data >> 2) & 1) ? 256 : 0;
+ *value += ((data >> 3) & 1) ? 512 : 0;
+ *value += ((data >> 4) & 1) ? 1024 : 0;
+ *value += ((data >> 5) & 1) ? 2048 : 0;
+ *value += 512;
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_ChrgCurr);
+ data &= BQ24193_CHRGCURR_20PCT_MASK;
+ if (data)
+ *value = *value * 20 / 100; // Fast charge current limit is 20%.
+ break;
+ case BQ24193_ChargeVoltageLimit: // Charge voltage limit (mV).
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_ChrgVolt);
+ data = (data & BQ24193_CHRGVOLT_VREG) >> 2;
+ *value += ((data >> 0) & 1) ? 16 : 0;
+ *value += ((data >> 1) & 1) ? 32 : 0;
+ *value += ((data >> 2) & 1) ? 64 : 0;
+ *value += ((data >> 3) & 1) ? 128 : 0;
+ *value += ((data >> 4) & 1) ? 256 : 0;
+ *value += ((data >> 5) & 1) ? 512 : 0;
+ *value += 3504;
+ break;
+ case BQ24193_RechargeThreshold: // Recharge voltage threshold less than voltage limit (mV).
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_ChrgVolt);
+ data &= BQ24193_IRTHERMAL_THERM_MASK;
+ if (data)
+ *value = 300;
+ else
+ *value = 100;
+ break;
+ case BQ24193_ThermalRegulation: // Thermal regulation threshold (oC).
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_IRCompThermal);
+ data &= BQ24193_IRTHERMAL_THERM_MASK;
+ switch (data)
+ {
+ case 0:
+ *value = 60;
+ break;
+ case 1:
+ *value = 80;
+ break;
+ case 2:
+ *value = 100;
+ break;
+ case 3:
+ *value = 120;
+ break;
+ }
+ break;
+ case BQ24193_ChargeStatus: // 0: Not charging, 1: Pre-charge, 2: Fast charging, 3: Charge termination done
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_Status);
+ *value = (data & BQ24193_STATUS_CHRG_MASK) >> 4;
+ break;
+ case BQ24193_TempStatus: // 0: Normal, 2: Warm, 3: Cool, 5: Cold, 6: Hot.
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_FaultReg);
+ *value = data & BQ24193_FAULT_THERM_MASK;
+ break;
+ case BQ24193_DevID: //Current now.
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_VendorPart);
+ *value = data & BQ24193_VENDORPART_DEV_MASK;
+ break;
+ case BQ24193_ProductNumber: //Current avg.
+ data = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_VendorPart);
+ *value = (data & BQ24193_VENDORPART_PN_MASK) >> 3;
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+static int _bq24193_write_verify_reg(u8 reg, u8 value)
+{
+ int retries = 8;
+ int ret;
+ u8 read_value;
+
+ do {
+ ret = i2c_send_byte(I2C_1, BQ24193_I2C_ADDR, reg, value);
+ read_value = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, reg);
+ if (read_value != value) {
+ ret = -1;
+ retries--;
+ }
+ } while (retries && read_value != value);
+
+ return ret;
+}
+
+void bq24193_fake_battery_removal()
+{
+ u8 value;
+
+ // Disable watchdog to keep BATFET disabled.
+ value = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_ChrgTermTimer);
+ value &= ~BQ24193_CHRGTERM_WATCHDOG_MASK;
+ _bq24193_write_verify_reg(BQ24193_ChrgTermTimer, value);
+
+ // Force BATFET to disabled state. This disconnects the battery from the system.
+ value = i2c_recv_byte(I2C_1, BQ24193_I2C_ADDR, BQ24193_Misc);
+ value |= BQ24193_MISC_BATFET_DI_MASK;
+ _bq24193_write_verify_reg(BQ24193_Misc, value);
+}
diff --git a/ipl/bq24193.h b/ipl/bq24193.h
new file mode 100644
index 0000000..a19c694
--- /dev/null
+++ b/ipl/bq24193.h
@@ -0,0 +1,119 @@
+/*
+* Battery charger driver for Nintendo Switch's TI BQ24193
+*
+* 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 __BQ24193_H_
+#define __BQ24193_H_
+
+#define BQ24193_I2C_ADDR 0x6B
+
+// REG 0 masks.
+#define BQ24193_INCONFIG_INLIMIT_MASK (7<<0)
+#define BQ24193_INCONFIG_VINDPM_MASK 0x78
+#define BQ24193_INCONFIG_HIZ_EN_MASK (1<<7)
+
+// REG 1 masks.
+#define BQ24193_PORCONFIG_BOOST_MASK (1<<0)
+#define BQ24193_PORCONFIG_SYSMIN_MASK (7<<1)
+#define BQ24193_PORCONFIG_CHGCONFIG_MASK (3<<4)
+#define BQ24193_PORCONFIG_I2CWATCHDOG_MASK (1<<6)
+#define BQ24193_PORCONFIG_RESET_MASK (1<<7)
+
+// REG 2 masks.
+#define BQ24193_CHRGCURR_20PCT_MASK (1<<0)
+#define BQ24193_CHRGCURR_ICHG_MASK 0xFC
+
+// REG 3 masks.
+#define BQ24193_PRECHRG_ITERM 0x0F
+#define BQ24193_PRECHRG_IPRECHG 0xF0
+
+// REG 4 masks.
+#define BQ24193_CHRGVOLT_VTHRES (1<<0)
+#define BQ24193_CHRGVOLT_BATTLOW (1<<1)
+#define BQ24193_CHRGVOLT_VREG 0xFC
+
+// REG 5 masks.
+#define BQ24193_CHRGTERM_ISET_MASK (1<<0)
+#define BQ24193_CHRGTERM_CHGTIMER_MASK (3<<1)
+#define BQ24193_CHRGTERM_ENTIMER_MASK (1<<3)
+#define BQ24193_CHRGTERM_WATCHDOG_MASK (3<<4)
+#define BQ24193_CHRGTERM_TERM_ST_MASK (1<<6)
+#define BQ24193_CHRGTERM_TERM_EN_MASK (1<<7)
+
+// REG 6 masks.
+#define BQ24193_IRTHERMAL_THERM_MASK (3<<0)
+#define BQ24193_IRTHERMAL_VCLAMP_MASK (7<<2)
+#define BQ24193_IRTHERMAL_BATTCOMP_MASK (7<<5)
+
+// REG 7 masks.
+#define BQ24193_MISC_INT_MASK (3<<0)
+#define BQ24193_MISC_VSET_MASK (1<<4)
+#define BQ24193_MISC_BATFET_DI_MASK (1<<5)
+#define BQ24193_MISC_TMR2X_EN_MASK (1<<6)
+#define BQ24193_MISC_DPDM_EN_MASK (1<<7)
+
+// REG 8 masks.
+#define BQ24193_STATUS_VSYS_MASK (1<<0)
+#define BQ24193_STATUS_THERM_MASK (1<<1)
+#define BQ24193_STATUS_PG_MASK (1<<2)
+#define BQ24193_STATUS_DPM_MASK (1<<3)
+#define BQ24193_STATUS_CHRG_MASK (3<<4)
+#define BQ24193_STATUS_VBUS_MASK (3<<6)
+
+// REG 9 masks.
+#define BQ24193_FAULT_THERM_MASK (7<<0)
+#define BQ24193_FAULT_BATT_OVP_MASK (1<<3)
+#define BQ24193_FAULT_CHARGE_MASK (3<<4)
+#define BQ24193_FAULT_BOOST_MASK (1<<6)
+#define BQ24193_FAULT_WATCHDOG_MASK (1<<7)
+
+// REG A masks.
+#define BQ24193_VENDORPART_DEV_MASK (3<<0)
+#define BQ24193_VENDORPART_PN_MASK (7<<3)
+
+enum BQ24193_reg {
+ BQ24193_InputSource = 0x00,
+ BQ24193_PORConfig = 0x01,
+ BQ24193_ChrgCurr = 0x02,
+ BQ24193_PreChrgTerm = 0x03,
+ BQ24193_ChrgVolt = 0x04,
+ BQ24193_ChrgTermTimer = 0x05,
+ BQ24193_IRCompThermal = 0x06,
+ BQ24193_Misc = 0x07,
+ BQ24193_Status = 0x08,
+ BQ24193_FaultReg = 0x09,
+ BQ24193_VendorPart = 0x0A,
+};
+
+enum BQ24193_reg_prop {
+ BQ24193_InputVoltageLimit, // REG 0.
+ BQ24193_IputCurrentLimit, // REG 0.
+ BQ24193_SystemMinimumVoltage, // REG 1.
+ BQ24193_FastChargeCurrentLimit, // REG 2.
+ BQ24193_ChargeVoltageLimit, // REG 4.
+ BQ24193_RechargeThreshold, // REG 4.
+ BQ24193_ThermalRegulation, // REG 6.
+ BQ24193_ChargeStatus, // REG 8.
+ BQ24193_TempStatus, // REG 9.
+ BQ24193_DevID, // REG A.
+ BQ24193_ProductNumber, // REG A.
+};
+
+int bq24193_get_property(enum BQ24193_reg_prop prop, int *value);
+void bq24193_fake_battery_removal();
+
+#endif /* __BQ24193_H_ */
diff --git a/ipl/main.c b/ipl/main.c
index d13a621..588dcc9 100755
--- a/ipl/main.c
+++ b/ipl/main.c
@@ -52,6 +52,8 @@
#include "pkg1.h"
#include "mmc.h"
#include "lz.h"
+#include "max17050.h"
+#include "bq24193.h"
//TODO: ugly.
gfx_ctxt_t gfx_ctxt;
@@ -1457,6 +1459,133 @@ void fix_sd_attr(){
btn_wait();
}
+void print_fuel_gauge_regs()
+{
+ gfx_clear_grey(&gfx_ctxt, 0x1B);
+ gfx_con_setpos(&gfx_con, 0, 0);
+
+ u8 *buf = (u8 *)malloc(0x100 * 2);
+
+ gfx_printf(&gfx_con, "%kBattery Fuel Gauge Registers:\n\n%k", 0xFF00DDFF, 0xFFCCCCCC);
+
+ for (int i = 0; i < 0x200; i += 2)
+ {
+ i2c_recv_buf_small(buf + i, 2, I2C_1, 0x36, i >> 1);
+ sleep(5000);
+ }
+
+ gfx_hexdump(&gfx_con, 0, (u8 *)buf, 0x200);
+
+ gfx_puts(&gfx_con, "\nPress POWER to dump them to SD Card.\nPress VOL to go to the menu.\n");
+
+ u32 btn = btn_wait();
+
+ if (btn & BTN_POWER)
+ {
+ if (sd_mount())
+ {
+ char fuelFilename[28];
+ f_mkdir("Backup/Dumps");
+ memcpy(fuelFilename, "Backup/Dumps/fuel_gauge.bin\0", 28);
+
+ if (sd_save_to_file((u8 *)buf, 0x200, fuelFilename))
+ EPRINTF("\nError creating fuel.bin file.");
+ else
+ gfx_puts(&gfx_con, "\nDone!\n");
+ sd_unmount();
+ }
+
+ btn_wait();
+ }
+ free(buf);
+}
+
+void fix_fuel_gauge_configuration()
+{
+ gfx_clear_grey(&gfx_ctxt, 0x1B);
+ gfx_con_setpos(&gfx_con, 0, 0);
+
+ int battVoltage, avgCurrent;
+
+ max17050_get_property(MAX17050_VCELL, &battVoltage);
+ max17050_get_property(MAX17050_AvgCurrent, &avgCurrent);
+
+ //Check if still charging. If not, check if battery is >= 95% (4.1V).
+ if (avgCurrent < 0 && battVoltage > 4100)
+ {
+ if ((avgCurrent / 1000) < -10)
+ EPRINTF("You need to be connected to a wall adapter,\nto apply this fix!");
+ else
+ {
+ gfx_printf(&gfx_con, "%kAre you really sure?\nThis will reset your fuel gauge completely!\n", 0xFFFFDD00);
+ gfx_printf(&gfx_con, "Additionally this will power off your console.\n%k", 0xFFCCCCCC);
+
+ gfx_puts(&gfx_con, "\nPress POWER to Continue.\nPress VOL to go to the menu.\n\n\n");
+
+ u32 btn = btn_wait();
+ if (btn & BTN_POWER)
+ {
+ max17050_fix_configuration();
+ sleep(1000000);
+ gfx_con_getpos(&gfx_con, &gfx_con.savedx, &gfx_con.savedy);
+ u16 value = 0;
+ gfx_printf(&gfx_con, "%kThe console will power off in 45 seconds.\n%k", 0xFFFFDD00, 0xFFCCCCCC);
+ while (value < 46)
+ {
+ gfx_con_setpos(&gfx_con, gfx_con.savedx, gfx_con.savedy);
+ gfx_printf(&gfx_con, "%2ds elapsed", value);
+ sleep(1000000);
+ value++;
+ }
+ sleep(2000000);
+
+ power_off();
+ }
+ return;
+ }
+ }
+ else
+ EPRINTF("You need a fully charged battery\nand connected to a wall adapter,\nto apply this fix!");
+
+ sleep(500000);
+ btn_wait();
+}
+
+void fix_battery_desync()
+{
+ int avgCurrent;
+
+ gfx_clear_grey(&gfx_ctxt, 0x1B);
+ gfx_con_setpos(&gfx_con, 0, 0);
+
+ gfx_printf(&gfx_con, "%kAre you really sure?\nThis will wipe your battery stats completely!\n", 0xFFFFDD00);
+ gfx_printf(&gfx_con, "\nAdditionally you may need to reconfigure,\nyour time and date settings.\n%k", 0xFFCCCCCC);
+
+ gfx_puts(&gfx_con, "\nPress POWER to Continue.\nPress VOL to go to the menu.\n\n\n");
+ u32 btn = btn_wait();
+ if (btn & BTN_POWER)
+ {
+ gfx_clear_grey(&gfx_ctxt, 0x1B);
+ gfx_con_setpos(&gfx_con, 0, 0);
+ gfx_printf(&gfx_con, "%kDisconnect the USB cable and wait 30 seconds.\naAfter that press any key!%k\n\n", 0xFFFFDD00, 0xFFCCCCCC);
+ gfx_printf(&gfx_con, "%k* After this process is done,\n connect the USB cable to power-on.\n\n%k", 0xFF00DDFF, 0xFFCCCCCC);
+ sleep(500000);
+ btn_wait();
+
+ //Check if still connected.
+ max17050_get_property(MAX17050_AvgCurrent, &avgCurrent);
+ if (avgCurrent < -100000)
+ {
+ bq24193_fake_battery_removal();
+ gfx_printf(&gfx_con, "If the device did not powered off,\ndo it from hekate menu");
+ }
+ else
+ EPRINTF("You need to be disconnected from USB,\nto apply this fix!");
+ sleep(500000);
+ btn_wait();
+ }
+}
+
void about()
{
static const char octopus[] =
@@ -1506,6 +1635,9 @@ ment_t ment_cinfo[] = {
MDEF_CAPTION("-- Storage Info --", 0xFF0AB9E6),
MDEF_HANDLER("Print eMMC info", print_mmc_info),
MDEF_HANDLER("Print SD Card info", print_sdcard_info),
+ MDEF_CHGLINE(),
+ MDEF_CAPTION("------ Misc ------", 0xFF0AB9E6),
+ MDEF_HANDLER("Print fuel gauge info", print_fuel_gauge_regs),
MDEF_END()
};
menu_t menu_cinfo = {
@@ -1549,6 +1681,8 @@ ment_t ment_tools[] = {
MDEF_CAPTION("------ Misc -------", 0xFF0AB9E6),
MDEF_HANDLER("Dump package1", dump_package1),
MDEF_HANDLER("Fix SD files attributes", fix_sd_attr),
+ MDEF_HANDLER("Fix battery de-sync", &fix_battery_desync),
+ //MDEF_MENU("Fix fuel gauge configuration", &fix_fuel_gauge_configuration),
MDEF_CHGLINE(),
MDEF_CAPTION("---- Dangerous ----", 0xFFFF0000),
MDEF_MENU("AutoRCM", &menu_autorcm),
diff --git a/ipl/max17050.c b/ipl/max17050.c
new file mode 100644
index 0000000..f05365d
--- /dev/null
+++ b/ipl/max17050.c
@@ -0,0 +1,373 @@
+/*
+ * Fuel gauge driver for Nintendo Switch's Maxim 17050
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham
+ * Copyright (C) 2018 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This driver is based on max17040_battery.c
+ */
+
+#include "max17050.h"
+#include "i2c.h"
+#include "util.h"
+
+/* Status register bits */
+#define STATUS_POR_BIT (1 << 1)
+#define STATUS_BST_BIT (1 << 3)
+#define STATUS_VMN_BIT (1 << 8)
+#define STATUS_TMN_BIT (1 << 9)
+#define STATUS_SMN_BIT (1 << 10)
+#define STATUS_BI_BIT (1 << 11)
+#define STATUS_VMX_BIT (1 << 12)
+#define STATUS_TMX_BIT (1 << 13)
+#define STATUS_SMX_BIT (1 << 14)
+#define STATUS_BR_BIT (1 << 15)
+
+/* Interrupt mask bits */
+#define CONFIG_ALRT_BIT_ENBL (1 << 2)
+#define STATUS_INTR_SOCMIN_BIT (1 << 10)
+#define STATUS_INTR_SOCMAX_BIT (1 << 14)
+
+#define VFSOC0_LOCK 0x0000
+#define VFSOC0_UNLOCK 0x0080
+
+#define dP_ACC_100 0x1900
+#define dP_ACC_200 0x3200
+
+#define MAX17050_VMAX_TOLERANCE 50 /* 50 mV */
+
+static int _max17050_get_temperature(int *temp)
+{
+ u16 data;
+
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_TEMP);
+
+ *temp = (s16)data;
+ /* The value is converted into deci-centigrade scale */
+ /* Units of LSB = 1 / 256 degree Celsius */
+ *temp = *temp * 10 / 256;
+ return 0;
+}
+
+int _max17050_get_status(int *status)
+{
+ int charge_full, charge_now;
+ int avg_current;
+ u16 data;
+
+ /*
+ * The MAX170xx has builtin end-of-charge detection and will update
+ * FullCAP to match RepCap when it detects end of charging.
+ *
+ * When this cycle the battery gets charged to a higher (calculated)
+ * capacity then the previous cycle then FullCAP will get updated
+ * contineously once end-of-charge detection kicks in, so allow the
+ * 2 to differ a bit.
+ */
+
+ i2c_recv_buf_small((u8 *)&charge_full, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_FullCAP);
+
+ i2c_recv_buf_small((u8 *)&charge_now, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_RepCap);
+
+ if ((charge_full - charge_now) <= MAX17050_FULL_THRESHOLD) {
+ *status = 0xFF; //FULL
+ return 0;
+ }
+
+ /*
+ * Even though we are supplied, we may still be discharging if the
+ * supply is e.g. only delivering 5V 0.5A. Check current if available.
+ */
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_AvgCurrent);
+
+ avg_current = (s16)data;
+ avg_current *= 1562500 / MAX17050_DEFAULT_SNS_RESISTOR;
+
+ if (avg_current > 0)
+ *status = 0x1; //Charging
+ else
+ *status = 0x0; //Discharging
+
+ return 0;
+}
+
+int _max17050_get_battery_health(int *health)
+{
+ int temp, vavg, vbatt;
+ u16 val;
+
+ i2c_recv_buf_small((u8 *)&val, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_AvgVCELL);
+ /* bits [0-3] unused */
+ vavg = val * 625 / 8;
+ /* Convert to millivolts */
+ vavg /= 1000;
+
+ i2c_recv_buf_small((u8 *)&val, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_VCELL);
+ /* bits [0-3] unused */
+ vbatt = val * 625 / 8;
+ /* Convert to millivolts */
+ vbatt /= 1000;
+
+ if (vavg < MAX17050_DEFAULT_VMIN) {
+ *health = HEALTH_DEAD;
+ goto out;
+ }
+
+ if (vbatt > MAX17050_DEFAULT_VMAX + MAX17050_VMAX_TOLERANCE) {
+ *health = HEALTH_OVERVOLTAGE;
+ goto out;
+ }
+
+ _max17050_get_temperature(&temp);
+
+ if (temp < MAX17050_DEFAULT_TEMP_MIN) {
+ *health = HEALTH_COLD;
+ goto out;
+ }
+
+ if (temp > MAX17050_DEFAULT_TEMP_MAX) {
+ *health = HEALTH_OVERHEAT;
+ goto out;
+ }
+
+ *health = HEALTH_GOOD; // 1
+
+out:
+ return 0;
+}
+
+int max17050_get_property(enum MAX17050_reg reg, int *value)
+{
+ u16 data;
+
+ switch (reg) {
+ //case 0x101://///////////////////////////FIX
+ // _max17050_get_status(value);
+ // break;
+ case MAX17050_Cycles: //Cycle count.
+ i2c_recv_buf_small((u8 *)value, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_Cycles);
+ break;
+ case MAX17050_MinMaxVolt: //Voltage max/min
+ i2c_recv_buf_small((u8 *)value, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_MinMaxVolt);
+ //value = (data >> 8) * 20; /* Voltage MAX. Units of LSB = 20mV */
+ //value = (data & 0xff) * 20; /* Voltage MIN. Units of 20mV */
+ *value = data;
+ break;
+ case MAX17050_V_empty: //Voltage min design.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_V_empty);
+ *value = (data >> 7) * 10; /* Units of LSB = 10mV */
+ break;
+ case MAX17050_VCELL: //Voltage now.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_VCELL);
+ *value = data * 625 / 8 / 1000;
+ break;
+ case MAX17050_AvgVCELL: //Voltage avg.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_AvgVCELL);
+ *value = data * 625 / 8 / 1000;
+ break;
+ case MAX17050_OCVInternal: //Voltage ocv.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_OCVInternal);
+ *value = data * 625 / 8 / 1000;
+ break;
+ case MAX17050_RepSOC: //Capacity %.
+ i2c_recv_buf_small((u8 *)value, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_RepSOC);
+ break;
+ case MAX17050_DesignCap: //Charge full design.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_DesignCap);
+ data = data * 5 / 10;
+ *value = data;
+ break;
+ case MAX17050_FullCAP: //Charge full.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_FullCAP);
+ data = data * 5 / 10;
+ *value = data;
+ break;
+ case MAX17050_RepCap: //Charge now.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_RepCap);
+ data = data * 5 / 10;
+ *value = data;
+ break;
+ case MAX17050_TEMP: //Temp.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_TEMP);
+ *value = (s16)(data);
+ *value = *value * 10 / 256;
+ break;
+ //case 0x100: //FIX me
+ // _max17050_get_battery_health(value);
+ // break;
+ case MAX17050_Current: //Current now.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_Current);
+ *value = (s16)data;
+ *value *= 1562500 / MAX17050_DEFAULT_SNS_RESISTOR;
+ break;
+ case MAX17050_AvgCurrent: //Current avg.
+ i2c_recv_buf_small((u8 *)&data, 2, I2C_1, MAXIM17050_I2C_ADDR, MAX17050_AvgCurrent);
+ *value = (s16)data;
+ *value *= 1562500 / MAX17050_DEFAULT_SNS_RESISTOR;
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+static int _max17050_write_verify_reg(u8 reg, u16 value)
+{
+ int retries = 8;
+ int ret;
+ u16 read_value;
+
+ do {
+ ret = i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, reg, (u8 *)&value, 2);
+ i2c_recv_buf_small((u8 *)&read_value, 2, I2C_1, MAXIM17050_I2C_ADDR, reg);
+ if (read_value != value) {
+ ret = -1;
+ retries--;
+ }
+ } while (retries && read_value != value);
+
+ return ret;
+}
+
+static void _max17050_override_por(u8 reg, u16 value)
+{
+ if (value)
+ i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, reg, (u8 *)&value, 2);
+}
+
+static void _max17050_load_new_capacity_params()
+{
+ u16 fullcap, repSoc, dq_acc, dp_acc;
+
+ fullcap = 0x2476; // 4667mAh design capacity.
+ dq_acc = 0x10bc; // From a healthy fuel gauge.
+ dp_acc = 0x5e09; // =||=
+ repSoc = 0x6400; // 100%.
+
+
+ _max17050_write_verify_reg(MAX17050_RemCap, fullcap);
+ _max17050_write_verify_reg(MAX17050_RepCap, fullcap);
+
+ _max17050_write_verify_reg(MAX17050_dQacc, dq_acc);
+ _max17050_write_verify_reg(MAX17050_dPacc, dp_acc);
+
+ _max17050_write_verify_reg(MAX17050_FullCAP, fullcap);
+ //i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_DesignCap, (u8 *)&fullcap, 2);
+ _max17050_write_verify_reg(MAX17050_FullCAPNom, fullcap);
+ /* Update SOC register with new SOC */
+ i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_RepSOC, (u8 *)&repSoc, 2);
+}
+
+static void _max17050_reset_vfsoc0_reg()
+{
+ u16 lockVal = 0;
+ u16 vfSoc = 0x6440; // >100% for fully charged battery
+
+ lockVal = VFSOC0_UNLOCK;
+ i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_VFSOC0Enable, (u8 *)&lockVal, 2);
+
+ _max17050_write_verify_reg(MAX17050_VFSOC0, vfSoc);
+
+ lockVal = VFSOC0_LOCK;
+ i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_VFSOC0Enable, (u8 *)&lockVal, 2);
+}
+
+static void _max17050_update_capacity_regs()
+{
+ u16 value = 0x2476; //Set to 4667mAh design capacity.
+ _max17050_write_verify_reg(MAX17050_FullCAP, value);
+ _max17050_write_verify_reg(MAX17050_FullCAPNom, value);
+ //i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_DesignCap, config->design_cap, 2);
+}
+
+static void _max17050_write_config_regs()
+{
+ u16 value = 0;
+
+ value = 0x7254;
+ i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_CONFIG, (u8 *)&value, 2);
+ value = 0x2473;
+ i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_LearnCFG, (u8 *)&value, 2);
+ //i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_FilterCFG, (u8 *)&value, 2)
+ //i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_RelaxCFG, (u8 *)&value, 2)
+ //i2c_send_buf_small(I2C_1, MAXIM17050_I2C_ADDR, MAX17050_FullSOCThr, (u8 *)&value, 2)
+}
+
+/*
+ * Block write all the override values coming from platform data.
+ * This function MUST be called before the POR initialization proceedure
+ * specified by maxim.
+ */
+static void _max17050_override_por_values()
+{
+ u16 dq_acc = 0x10bc; // From a healthy fuel gauge.
+ u16 dp_acc = 0x5e09; // =||=
+
+ _max17050_override_por(MAX17050_dQacc, dq_acc);
+ _max17050_override_por(MAX17050_dPacc, dp_acc);
+
+ //_max17050_override_por(MAX17050_RCOMP0, config->rcomp0); //0x58
+ //_max17050_override_por(MAX17050_TempCo, config->tcompc0); //0x1b22
+
+ //u16 k_empty0 = 0x439;
+ //_max17050_override_por(map, MAX17050_K_empty0, k_empty0); // unknown cell data
+}
+
+static void _max17050_set_por_bit(u16 value)
+{
+ _max17050_write_verify_reg(MAX17050_STATUS, value);
+}
+
+int max17050_fix_configuration()
+{
+ /* Init phase, set the POR bit */
+ _max17050_set_por_bit(STATUS_POR_BIT);
+
+ /* Override POR values */
+ _max17050_override_por_values();
+ /* After Power up, the MAX17050 requires 500ms in order
+ * to perform signal debouncing and initial SOC reporting
+ */
+ sleep(500000);
+
+ /* Initialize configaration */
+ _max17050_write_config_regs();
+
+
+ /* update capacity params */
+ _max17050_update_capacity_regs();
+
+ /* delay must be atleast 350mS to allow VFSOC
+ * to be calculated from the new configuration
+ */
+ sleep(350000);
+
+ /* reset vfsoc0 reg */
+ _max17050_reset_vfsoc0_reg();
+
+ /* load new capacity params */
+ _max17050_load_new_capacity_params();
+
+ /* Init complete, Clear the POR bit */
+ //_max17050_set_por_bit(0); // Should we? Or let the switch to reconfigure POR?
+
+ // Sets POR, BI, BR.
+ _max17050_set_por_bit(0x8801);
+
+ return 0;
+}
diff --git a/ipl/max17050.h b/ipl/max17050.h
new file mode 100644
index 0000000..ac99f70
--- /dev/null
+++ b/ipl/max17050.h
@@ -0,0 +1,138 @@
+/*
+ * Fuel gauge driver for Nintendo Switch's Maxim 17050
+ * Note that Maxim 8966 and 8997 are mfd and this is its subdevice.
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham
+ * Copyright (C) 2018 CTCaer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __MAX17050_H_
+#define __MAX17050_H_
+
+#define MAX17050_STATUS_BattAbsent (1 << 3)
+#define MAX17050_BATTERY_FULL 95 /* Recommend. FullSOCThr value */
+#define MAX17050_DEFAULT_SNS_RESISTOR 10000
+#define MAX17050_DEFAULT_VMIN 3200
+#define MAX17050_DEFAULT_VMAX 4200 /* LiHV cell max */
+#define MAX17050_DEFAULT_TEMP_MIN 5 /* For sys without temp sensor */
+#define MAX17050_DEFAULT_TEMP_MAX 650 /* 65 degrees Celcius */
+
+/* Consider RepCap which is less then 10 units below FullCAP full */
+#define MAX17050_FULL_THRESHOLD 10
+
+#define MAX17050_CHARACTERIZATION_DATA_SIZE 48
+
+#define MAXIM17050_I2C_ADDR 0x36
+
+enum MAX17050_reg {
+ MAX17050_STATUS = 0x00,
+ MAX17050_VALRT_Th = 0x01,
+ MAX17050_TALRT_Th = 0x02,
+ MAX17050_SALRT_Th = 0x03,
+ MAX17050_AtRate = 0x04,
+ MAX17050_RepCap = 0x05,
+ MAX17050_RepSOC = 0x06,
+ MAX17050_Age = 0x07,
+ MAX17050_TEMP = 0x08,
+ MAX17050_VCELL = 0x09,
+ MAX17050_Current = 0x0A,
+ MAX17050_AvgCurrent = 0x0B,
+
+ MAX17050_SOC = 0x0D,
+ MAX17050_AvSOC = 0x0E,
+ MAX17050_RemCap = 0x0F,
+ MAX17050_FullCAP = 0x10,
+ MAX17050_TTE = 0x11,
+ MAX17050_QRTbl00 = 0x12,
+ MAX17050_FullSOCThr = 0x13,
+ MAX17050_RSLOW = 0x14,
+
+ MAX17050_AvgTA = 0x16,
+ MAX17050_Cycles = 0x17,
+ MAX17050_DesignCap = 0x18,
+ MAX17050_AvgVCELL = 0x19,
+ MAX17050_MinMaxTemp = 0x1A,
+ MAX17050_MinMaxVolt = 0x1B,
+ MAX17050_MinMaxCurr = 0x1C,
+ MAX17050_CONFIG = 0x1D,
+ MAX17050_ICHGTerm = 0x1E,
+ MAX17050_AvCap = 0x1F,
+ MAX17050_ManName = 0x20,
+ MAX17050_DevName = 0x21,
+ MAX17050_QRTbl10 = 0x22,
+ MAX17050_FullCAPNom = 0x23,
+ MAX17050_TempNom = 0x24,
+ MAX17050_TempLim = 0x25,
+ MAX17050_TempHot = 0x26,
+ MAX17050_AIN = 0x27,
+ MAX17050_LearnCFG = 0x28,
+ MAX17050_FilterCFG = 0x29,
+ MAX17050_RelaxCFG = 0x2A,
+ MAX17050_MiscCFG = 0x2B,
+ MAX17050_TGAIN = 0x2C,
+ MAX17050_TOFF = 0x2D,
+ MAX17050_CGAIN = 0x2E,
+ MAX17050_COFF = 0x2F,
+
+ MAX17050_QRTbl20 = 0x32,
+ MAX17050_SOC_empty = 0x33,
+ MAX17050_T_empty = 0x34,
+ MAX17050_FullCAP0 = 0x35,
+ MAX17050_LAvg_empty = 0x36,
+ MAX17050_FCTC = 0x37,
+ MAX17050_RCOMP0 = 0x38,
+ MAX17050_TempCo = 0x39,
+ MAX17050_V_empty = 0x3A,
+ MAX17050_K_empty0 = 0x3B,
+ MAX17050_TaskPeriod = 0x3C,
+ MAX17050_FSTAT = 0x3D,
+
+ MAX17050_SHDNTIMER = 0x3F,
+ MAX17050_QRTbl30 = 0x42,
+ MAX17050_dQacc = 0x45,
+ MAX17050_dPacc = 0x46,
+
+ MAX17050_VFSOC0 = 0x48,
+
+ MAX17050_QH = 0x4D,
+ MAX17050_QL = 0x4E,
+
+ MAX17050_VFSOC0Enable = 0x60,
+
+ MAX17050_MODELChrTbl = 0x80,
+
+ MAX17050_OCV = 0xEE,
+
+ MAX17050_OCVInternal = 0xFB,
+
+ MAX17050_VFSOC = 0xFF,
+};
+
+enum {
+ HEALTH_UNKNOWN = 0,
+ HEALTH_GOOD,
+ HEALTH_OVERHEAT,
+ HEALTH_DEAD,
+ HEALTH_OVERVOLTAGE,
+ HEALTH_COLD,
+};
+
+int max17050_get_property(enum MAX17050_reg reg, int *value);
+int max17050_fix_configuration();
+
+#endif /* __MAX17050_H_ */
diff --git a/ipl/tui.c b/ipl/tui.c
index edb25fa..0c1843d 100755
--- a/ipl/tui.c
+++ b/ipl/tui.c
@@ -17,6 +17,7 @@
#include "tui.h"
#include "btn.h"
+#include "max17050.h"
#ifdef MENU_LOGO_ENABLE
extern u8 *Kc_MENU_LOGO;
@@ -50,6 +51,8 @@ void tui_pbar(gfx_con_t *con, int x, int y, u32 val, u32 fgcol, u32 bgcol)
void *tui_do_menu(gfx_con_t *con, menu_t *menu)
{
int idx = 0, prev_idx = 0, cnt = 0x7FFFFFFF;
+ u32 battPercent = 0;
+ int battVoltCurr = 0;
gfx_clear_grey(con->gfx_ctxt, 0x1B);
#ifdef MENU_LOGO_ENABLE
@@ -106,6 +109,24 @@ void *tui_do_menu(gfx_con_t *con, menu_t *menu)
gfx_con_setcol(con, 0xFFCCCCCC, 1, 0xFF1B1B1B);
gfx_putc(con, '\n');
+ // Print help and battery status.
+ gfx_con_getpos(con, &con->savedx, &con->savedy);
+ gfx_con_setpos(con, 0, 74 * 16);
+ gfx_printf(con, "%k VOL: Move up/down\n PWR: Select option\n\n", 0xFF555555);
+
+ max17050_get_property(MAX17050_RepSOC, (int *)&battPercent);
+ gfx_printf(con, " %d.%d%%", (battPercent >> 8) & 0xFF, (battPercent & 0xFF) / 26);
+ max17050_get_property(MAX17050_VCELL, &battVoltCurr);
+ gfx_printf(con, " (%d mV) ", battVoltCurr);
+ max17050_get_property(MAX17050_AvgCurrent, &battVoltCurr);
+ if (battVoltCurr > 0)
+ gfx_printf(con, "\n %kCharging:%k %d mA %k\n",
+ 0xFF008000, 0xFF555555, battVoltCurr / 1000, 0xFFCCCCCC);
+ else
+ gfx_printf(con, "\n %kDischarging:%k -%d mA %k\n",
+ 0xFF800000, 0xFF555555, (~battVoltCurr) / 1000, 0xFFCCCCCC);
+ gfx_con_setpos(con, con->savedx, con->savedy);
+
// Wait for user command.
u32 btn = btn_wait();