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
This commit is contained in:
Kostas Missos 2018-06-24 22:38:32 +03:00
parent 6961769a86
commit 52506def30
6 changed files with 965 additions and 0 deletions

180
ipl/bq24193.c Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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);
}

119
ipl/bq24193.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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_ */

View file

@ -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),

373
ipl/max17050.c Normal file
View file

@ -0,0 +1,373 @@
/*
* Fuel gauge driver for Nintendo Switch's Maxim 17050
*
* Copyright (C) 2011 Samsung Electronics
* MyungJoo Ham <myungjoo.ham@samsung.com>
* 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;
}

138
ipl/max17050.h Normal file
View file

@ -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 <myungjoo.ham@samsung.com>
* 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_ */

View file

@ -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();