blob: b99b57c71d72a99e8ca7699937e1b1864feb95ad [file] [log] [blame]
/*
* Copyright (C) 2014 Marvell International Ltd.
* Yi Zhang <yizhang@marvell.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include "charge.h"
#include <power/power_chrg.h>
DECLARE_GLOBAL_DATA_PTR;
static u32 boot_uv = UBOOT_CHGBOOT_UV;
static u32 extern_power_flag = 0;
static u32 skip_poweroff_chg = 0;
extern unsigned int pmic_power_status;
static struct pmic *g_p_bat;
__weak void oled_show_mrvl_logo(u8 choice) {}
__weak void oled_show_no_battery(void) {}
__weak void oled_show_bat_soc(u8 percent) {}
__weak void oled_show_bat_charging(u16 percent) {}
__weak void oled_display_off(void) {}
__weak void charger_enable_bat_priority(void) {}
__weak void lcd_show_bat_soc(int percent) {}
__weak void lcd_show_no_battery(void) {}
__weak void lcd_show_mrvl_log(void) {}
__weak void lcd_panel_on(int on_off) {}
static struct pmic *init_charger_fg(u8 pmic_i2c_bus,
u8 chg_i2c_bus, u8 fg_i2c_bus)
{
int ret;
static int charger_fg_inited = 0;
struct pmic *p_fg, *p_chrg, *p_bat;
if (charger_fg_inited)
return g_p_bat;
ret = power_chrg_init(chg_i2c_bus);
if (ret) {
printf("init charger fails.\n");
return NULL;
}
ret = power_fg_init(fg_i2c_bus);
if (ret) {
printf("init fuelgauge fails.\n");
return NULL;
}
ret = power_bat_init(pmic_i2c_bus);
if (ret) {
printf("init charger/fuelgauge parent fails.\n");
return NULL;
}
p_chrg = pmic_get(MARVELL_PMIC_CHARGE);
if (!p_chrg) {
printf("%s: access charger fails\n", MARVELL_PMIC_CHARGE);
return NULL;
}
p_fg = pmic_get(MARVELL_PMIC_FG);
if (!p_fg) {
printf("%s: access fuelgauge fails\n", MARVELL_PMIC_FG);
return NULL;
}
p_bat = pmic_get(MARVELL_PMIC_BATT);
if (!p_bat) {
printf("%s: access charger/fuelgauge parent fails\n",
MARVELL_PMIC_BATT);
return NULL;
}
p_fg->parent = p_bat;
p_chrg->parent = p_bat;
p_bat->pbat->battery_init(p_bat, p_fg, p_chrg, NULL);
g_p_bat = p_bat;
charger_fg_inited = 1;
return p_bat;
}
static bool check_charger_fg(struct pmic *p_bat)
{
int charger_type;
struct pmic *p_chrg;
if (!p_bat || !p_bat->pbat || !p_bat->pbat->chrg) {
printf("charger is NULL\n");
return false;
}
p_chrg = p_bat->pbat->chrg;
charger_type = p_chrg->chrg->chrg_type(p_chrg);
printf("charger type: %d\n", charger_type);
if (p_bat->pbat->fg->fg->fg_battery_check(p_bat->pbat->fg, p_bat)) {
printf("fg check failed\n");
return false;
}
return true;
}
static bool is_bat_present(struct pmic *p_bat)
{
struct pmic *p_chrg;
if (!p_bat || !p_bat->pbat || !p_bat->pbat->chrg) {
printf("%s: charger is NULL\n", __FUNCTION__);
return false;
}
p_chrg = p_bat->pbat->chrg;
if (!p_chrg->chrg->chrg_bat_present(p_chrg)) {
printf("No bat found\n");
return false;
}
return true;
}
static int do_charging(struct pmic *p_bat, u32 target_uv, u32 boot_min_uv)
{
int ret;
if (!p_bat || !p_bat->pbat)
return CHG_STAT_NOT_READY;
if (p_bat->pbat->battery_charge) {
while (p_bat->pbat->bat->voltage_uV <= target_uv) {
ret = p_bat->pbat->battery_charge(p_bat);
if (ret == CHG_STAT_ONKEY_BOOT) {
#ifdef CONFIG_PWROFFCHG_IN_UBOOT
if (p_bat->pbat->bat->voltage_uV > UBOOT_CHGBOOT_UV) {
return CHG_STAT_ONKEY_BOOT;
} else {
/* onkey detected but battery is low */
printf("vbat too low: %d uV, does not boot up",
p_bat->pbat->bat->voltage_uV);
}
#endif
} else if (ret == CHG_STAT_BAT_LOST ||
ret == CHG_STAT_CHGR_LOST) {
/* return error stat and power off */
return ret;
}
/*
* other state is normal then we charge until target_uv is met
* if it's poweroff charge now, we continue charge untill
* charge remove to poweroff or onkey event to bootup
*/
}
}
return CHG_STAT_NORMAL;
}
/*
* [0, 0] --> no charge, only show uboot charging logo and set cmdline,
* for debug
* [0, lo_uv] --> charge only
* [lo_uv, hi_uv] --> charge and show uboot charging logo
* [hi_uv, )]--> not charge in uboot, set cmdline and boot
*
* function returns with following cases:
* a. charger removed and still doesn't charge to boot_min_uv
* b. battery removed and still doesn't charge to boot_min_uv
* c. battery charge to boot_min_uv and normal return
* d. long onkey detected but desn't meet boot_min_uv will not return
* e. battery full but do not detect onkey/charger remove/battery event won't return
*/
static int loop_charge(struct pmic *p_bat,
int lo_uv, u32 hi_uv, u32 boot_min_uv)
{
int ret = CHG_STAT_NORMAL;
if (!p_bat || !p_bat->pbat) {
printf("%s: battery information is NULL!\n", __func__);
return CHG_STAT_NOT_READY;
}
if (lo_uv > hi_uv)
lo_uv = hi_uv;
/* for sequence test */
if ((lo_uv == 0) && (hi_uv == 0)) {
/* TODO: show logo here */
return CHG_STAT_NORMAL;
}
/* clear bat data stored in pmic rtc domain */
pmic_clear_bat_strdata();
printf("\n%s begins...\n\n", __func__);
/* show initial soc */
/* oled_show_bat_soc(p_bat->pbat->bat->state_of_chrg); */
if (p_bat->pbat->bat->voltage_uV <= lo_uv) {
printf("charging phase #1\n");
ret = do_charging(p_bat, lo_uv, boot_min_uv);
if (ret != CHG_STAT_NORMAL)
goto out;
}
if (p_bat->pbat->bat->voltage_uV <= hi_uv) {
printf("charging phase #2\n");
ret = do_charging(p_bat, hi_uv, boot_min_uv);
if (ret != CHG_STAT_NORMAL)
goto out;
}
out:
printf("\n%s finishes..., ret: %d\n\n", __func__, ret);
return ret;
}
static bool add_charger_cmdline(void)
{
char *cmdline = malloc(COMMAND_LINE_SIZE);
if (!cmdline) {
printf("%s: alloc for cmdline fails.\n", __func__);
return false;
}
/*
* we need to access before the env is relocated,
* gd->env_buf is too small, don't use getenv();
*/
if (gd->flags & GD_FLG_ENV_READY) {
strncpy(cmdline, getenv("bootargs"), COMMAND_LINE_SIZE);
printf("%s: getenv()\n", __func__);
} else {
getenv_f("bootargs", cmdline, COMMAND_LINE_SIZE);
printf("%s: getenv_f()\n", __func__);
}
debug("%s: bootargs = %s\n", __func__, getenv("bootargs"));
sprintf(cmdline + strlen(cmdline), " asrbatp");
if (setenv("bootargs", cmdline)) {
printf("%s: set bootargs fails\n", __func__);
free(cmdline);
return false;
}
debug("%s: bootargs = %s\n", __func__, getenv("bootargs"));
free(cmdline);
return true;
}
static char *bootup_reason[] = {
"power off",
"onkey",
"usb charge",
"rtc alarm",
"fault wakeup",
"bat wakeup",
"reboot",
};
static void enable_pm80x_fg_cmdline(void)
{
char *cmdline;
cmdline = malloc(COMMAND_LINE_SIZE);
strncpy(cmdline, getenv("bootargs"), COMMAND_LINE_SIZE);
sprintf(cmdline + strlen(cmdline), " force_pm80x_fg");
setenv("bootargs", cmdline);
free(cmdline);
}
#ifndef CONFIG_POWER_88PM830
int detect_mrvl_charger(unsigned char bus)
{
static const char name[] = "mrvl_charger";
struct pmic *p = pmic_alloc();
if (!p) {
printf("%s: POWER allocation error!\n", __func__);
return -ENOMEM;
}
p->name = name;
p->interface = PMIC_I2C;
p->number_of_regs = 0x40;
p->hw.i2c.addr = 0x68;
p->hw.i2c.tx_num = 1;
p->bus = bus;
p->chrg = NULL;
if (pmic_probe(p)) {
printf("%s: no mrvl charger!\n", __func__);
return -ENODEV;
}
return 0;
}
#endif
/*
* this function name is from lagacy design, although we call it poweroff charge,
* some other check routine is also done in this function. we don't need to do
* any check in *_dkb.c otherwise we just call this function in *_dkb.c which
* makes it easy to maintain the code
*/
void power_off_charge(u32 *emmd_pmic, u8 pmic_i2c,
u8 chg_i2c, u8 fg_i2c, u32 lo_uv, u32 hi_uv)
{
int ret;
struct pmic *p_bat;
struct pmic_chip_desc *board_pmic_chip;
bool chg_fg_status, cmd_status, bat_present = false;
enum sys_boot_up_reason reason;
bool force_chg = !!((lo_uv == 0) && (hi_uv == 0));
if (skip_poweroff_chg)
return;
/* set bootargs force_pm80x_fg to choose pm80x_fg */
#ifndef CONFIG_POWER_88PM830
if (detect_mrvl_charger(chg_i2c))
enable_pm80x_fg_cmdline();
#endif
debug("lo_uv = %d, hi_uv = %d, force_chg = %d\n",
lo_uv, hi_uv, force_chg);
board_pmic_chip = get_marvell_pmic();
if (!board_pmic_chip) {
printf("--- %s: chip is NULL.\n", __func__);
goto out_bootup_nopmic;
}
reason = get_boot_up_reason(emmd_pmic);
if (reason < SYS_BR_MAX && reason >= 0)
printf("bootup reason: %s\n", bootup_reason[reason]);
p_bat = init_charger_fg(pmic_i2c, chg_i2c, fg_i2c);
if (!p_bat) {
printf("charger&bat probe fails!\n");
if (!force_chg)
goto out_bootup_no_charger;
}
/* we check if the battery is online again */
bat_present = is_bat_present(p_bat);
if (!bat_present) {
if (!force_chg) {
if (extern_power_flag == 1)
goto out_bootup_extern_supply;
else
goto out_bootup_nobat_noextern;
}
}
/* get battery initial status */
/* p_bat->fg->fg_battery_update(p_bat->pbat->fg, p_bat); */
if ((reason == SYS_BR_CHARGE) || force_chg) {
/* check TA type and fuel gauge chip */
chg_fg_status = check_charger_fg(p_bat);
if (!chg_fg_status) {
printf("charger/fuelgauge status is not right!\n");
if (!force_chg)
goto out_bootup_ta_fg_error;
}
/* TODO: disable backlight and cp-D2*/
/* setop(0); */
//printf("run cp_d2 to put cp/msa into LPM\n");
//run_command("cp_d2", 0);
/* charge until the system can boot up */
ret = loop_charge(p_bat, lo_uv, hi_uv, boot_uv);
if (ret == CHG_STAT_CHGR_LOST || ret == CHG_STAT_BAT_LOST) {
printf("chgr or bat is removed, power down!\n");
goto charger_bat_lost_pd;
}
/* if charger is removed, shut down the device */
if (!p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
printf("charger is removed, power down!\n");
goto charger_bat_lost_pd;
}
/* long onekey detected in power off loop */
goto out_bootup_lonkey_from_pchg;
} else if (reason == SYS_BR_ONKEY) {
/* this is very rare to happen, wakeup by onkey with charger */
if (p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
/*
* onkey boot with charger, this case is very rare
* battery need charge to UBOOT_CHGBOOT_UV
*/
if (p_bat->pbat->bat->voltage_uV <= UBOOT_CHGBOOT_UV) {
/* detect if charger is online again */
if (p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
/* charge until the system can boot up */
ret = loop_charge(p_bat, lo_uv, UBOOT_CHGBOOT_UV, boot_uv);
if (ret == CHG_STAT_CHGR_LOST || ret == CHG_STAT_BAT_LOST) {
printf("chgr or bat is removed, power down!\n");
goto charger_bat_lost_pd;
}
/*
* charged to UBOOT_CHGBOOT_UV and returns now
* if charger is removed, shut down the device
*/
if (!p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
printf("charger is removed, power down!\n");
goto charger_bat_lost_pd;
}
/* onkey and chg to normal voltage */
goto out_bootup_lonkey_chgrdy;
} else {
/* display batter low and power down */
printf("battery need charge but without charger"
", power down\n");
goto bat_low_pd;
}
} else
goto out_bootup_lonkey_nochg;
} else
goto out_bootup_lonkey_nochg;
} else if (reason == SYS_BR_RTC_ALARM) {
/* RTC ALARM wakeup */
goto out_bootup_rtc;
} else if (reason == SYS_BR_FAULT_WAKEUP) {
/* fault wakeup: such as kernel panic or pmic reset */
goto out_bootup_fault_wakeup;
} else {
/*
* kernel reboot
*/
printf("%s: do nothing for charge.\n", __func__);
/* check if bat is still there again */
chg_fg_status = check_charger_fg(p_bat);
if (!chg_fg_status) {
printf("ta&fg detection failed!\n");
goto out_bootup_ta_fg_error;
}
/*
* here we choose 3.45V as power down voltage
*/
if (p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
/* battery need charge to UBOOT_CHGBOOT_UV
* reboot with low voltage, this is very rare to happen
* ususally system already powers down at this point
*/
if (p_bat->pbat->bat->voltage_uV <= UBOOT_CHGBOOT_UV) {
/* charger is connected */
if (p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
/* charge until the system can boot up */
ret = loop_charge(p_bat, lo_uv, UBOOT_CHGBOOT_UV, boot_uv);
if (ret == CHG_STAT_CHGR_LOST || ret == CHG_STAT_BAT_LOST) {
printf("chgr or bat is removed, power down!\n");
goto charger_bat_lost_pd;
}
/* if charger is removed, shut down the device */
if (!p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
printf("charger is removed, power down!\n");
goto charger_bat_lost_pd;
}
goto out_bootup_reboot_chg_rdy;
} else {
/* display batter low and power down */
printf("battery need charge but without charger"
", power down\n");
goto bat_low_pd;
}
} else {
/* reboot with charger connected */
goto out_bootup_reboot_with_ta_vrdy;
}
} else
goto out_bootup_reboot_no_ta;
}
/* boot up and display logo in below cases */
out_bootup_reboot_with_ta_vrdy:
out_bootup_reboot_no_ta:
out_bootup_reboot_chg_rdy:
out_bootup_rtc:
out_bootup_lonkey_chgrdy:
out_bootup_lonkey_nochg:
out_bootup_lonkey_from_pchg:
out_bootup_cmdline_error:
out_bootup_ta_fg_error:
out_bootup_no_charger:
out_bootup_nopmic:
out_bootup_fault_wakeup:
/*
* voltage is ok so we can switch to battery priority mode
*/
charger_enable_bat_priority();
oled_show_mrvl_logo(0);
lcd_show_mrvl_log();
#ifdef CONFIG_FG_PM803
if (bat_present && pmic_is_pm803()) {
cmd_status = add_charger_cmdline();
if (!cmd_status)
printf("add charger cmdline failes");
}
#endif
return;
/* show bat low and power down */
bat_low_pd:
oled_show_bat_soc(0);
lcd_show_bat_soc(0);
mdelay(500);
/* charger removed in power off chg loop, turn off display and power down */
charger_bat_lost_pd:
oled_display_off();
lcd_panel_on(0);
pmic_enter_powerdown(board_pmic_chip);
/* no battery case(dkb) and external power supply case */
out_bootup_extern_supply:
charger_enable_bat_priority();
out_bootup_nobat_noextern:
oled_show_no_battery();
lcd_show_no_battery();
return;
}
/*
* this function is called at very early stage where lcd/led is not inited yet,
* we just check whether we should power down or can go further, in the next step
* we will check what we should do in power off charge function
*/
void check_sys_boot_or_pd(u32 *emmd_pmic, u8 pmic_i2c, u8 chg_i2c, u8 fg_i2c)
{
int i;
bool cmd_status;
struct pmic *p_bat;
struct pmic_chip_desc *board_pmic_chip;
enum sys_boot_up_reason reason;
board_pmic_chip = get_marvell_pmic();
if (!board_pmic_chip) {
printf("--- %s: chip is NULL.\n", __func__);
goto out_boot_further;
}
reason = get_boot_up_reason(emmd_pmic);
printf("boot up reason = %d\n", reason);
p_bat = init_charger_fg(pmic_i2c, chg_i2c, fg_i2c);
if (!p_bat) {
printf("battery initializtion fails!\n");
goto out_boot_further;
}
/* get battery initial status
* don't update bat soc for restart case
*/
if (SYS_BR_REBOOT != reason) {
p_bat->fg->fg_battery_update(p_bat->pbat->fg, p_bat, 0);
printf("vbat: %d uv\n", p_bat->pbat->bat->voltage_uV);
} else {
skip_poweroff_chg = 1;
goto out_boot_further;
}
/* if charger is not connected with */
if (!is_bat_present(p_bat)) {
/*
* bat is not present while the charger ic is alive,
* this is probably powered by external power supply
*/
if (p_bat->pbat->bat->voltage_uV > (3100 * 1000)) {
extern_power_flag = 1;
printf("!!!external power supply case\n");
} else {
extern_power_flag = 0;
printf("no external power supply\n");
}
goto out_boot_further;
}
if (reason == SYS_BR_ONKEY) {
/*
* onkey boot without charger
*/
if (!p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
/* battery is too low and even don't turn on disp */
if (p_bat->pbat->bat->voltage_uV < BAT_LOW_UV) {
printf("batt volt is low (%dmV) and charger is not present, power down\n",
p_bat->pbat->bat->voltage_uV / 1000);
pmic_enter_powerdown(board_pmic_chip);
} else {
/* Battery is enough to boot */
i = 10;
while (i > 0) {
i--;
udelay(ONKEY_BOOT_CHECK_USEC/10);
if (!marvell88pm_get_onkey())
goto onkey_boot_pd;
}
printf("%s: long onkey deteced, boot...\n", __func__);
goto out_boot_further;
}
}
} else if (reason == SYS_BR_BAT_WAKEUP || reason == SYS_BR_POWER_OFF) {
printf("Power down on bat wakeup\n");
pmic_enter_powerdown(board_pmic_chip);
} else if (reason == SYS_BR_RTC_ALARM) {
/* RTC ALARM wakeup */
goto out_boot_further;
} else if (reason == SYS_BR_FAULT_WAKEUP) {
/* fault wakeup: such as kernel panic or pmic reset */
goto out_boot_further;
} else {
/*
* here we choose 3.45V as power down voltage
*/
if (!p_bat->chrg->chrg_type(p_bat->pbat->chrg)) {
/* no charger and battery is too low */
if (p_bat->pbat->bat->voltage_uV < BAT_LOW_UV) {
printf("batt volt is low (%dmV) and charger is not present, power down\n",
p_bat->pbat->bat->voltage_uV / 1000);
pmic_enter_powerdown(board_pmic_chip);
} else
goto out_boot_further;
}
}
out_boot_further:
#ifdef CONFIG_FG_PM803
if (skip_poweroff_chg && pmic_is_pm803() && is_bat_present(p_bat)) {
cmd_status = add_charger_cmdline();
if (!cmd_status)
printf("add charger cmdline failes");
}
#endif
return;
onkey_boot_pd:
printf("onkey boot: long onkey not detected\n");
printf("power off now ...\n");
pmic_enter_powerdown(board_pmic_chip);
}
int do_poff_chg(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
u8 pmic_i2c, chg_i2c, fg_i2c;
u32 *emmd_pmic;
u32 lo_uv, hi_uv;
if (argc != 7)
return -1;
emmd_pmic = (void *)simple_strtoul(argv[1], NULL, 16);
pmic_i2c = simple_strtoul(argv[2], NULL, 16);
chg_i2c = simple_strtoul(argv[3], NULL, 16);
fg_i2c = simple_strtoul(argv[4], NULL, 16);
lo_uv = simple_strtoul(argv[5], NULL, 10);
hi_uv = simple_strtoul(argv[6], NULL, 10);
debug("emmd_pmic = 0x%x\n", *emmd_pmic);
power_off_charge((u32 *)&pmic_power_status, pmic_i2c, chg_i2c, fg_i2c,
lo_uv, hi_uv);
return 0;
}
static char help_text[] =
"Usage:\n"
"pwroffchg emmd_pmic_addr pmic_i2c_number chg_i2c_number fg_i2c_number"
" low_volt high_volt\n"
"if you choose low_volt and high_volt as 0, it enters into test mode:\n"
"only show uboot charging logo and set cmmdline\n"
"for example:\n"
"pwroffchg 0x8140040 2 2 2 0 0"
"";
U_BOOT_CMD(
pwroffchg, 7, 1, do_poff_chg,
"Test power off charging", help_text
);