From 52506def30d2ea18ce4ccad3546567fa082cfc2f Mon Sep 17 00:00:00 2001 From: Kostas Missos Date: Sun, 24 Jun 2018 22:38:32 +0300 Subject: [PATCH] Add Battery Charger & Fuel Gauge dirvers * Add Fuel gauge configuration fix * Add Battery de-sync fix * Fuel gauge registers dumping * Add help and battery status in menu --- ipl/bq24193.c | 180 ++++++++++++++++++++++++ ipl/bq24193.h | 119 ++++++++++++++++ ipl/main.c | 134 ++++++++++++++++++ ipl/max17050.c | 373 +++++++++++++++++++++++++++++++++++++++++++++++++ ipl/max17050.h | 138 ++++++++++++++++++ ipl/tui.c | 21 +++ 6 files changed, 965 insertions(+) create mode 100644 ipl/bq24193.c create mode 100644 ipl/bq24193.h create mode 100644 ipl/max17050.c create mode 100644 ipl/max17050.h 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();