You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1140 lines
28 KiB

/*
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <asm/io.h>
#include <common.h>
#include <boot_rkimg.h>
#include <console.h>
#include <dm.h>
#include <errno.h>
#include <key.h>
#include <led.h>
#include <rtc.h>
#include <pwm.h>
#include <asm/arch/rockchip_smccc.h>
#include <asm/suspend.h>
#include <linux/input.h>
#include <power/charge_display.h>
#include <power/charge_animation.h>
#include <power/rockchip_pm.h>
#include <power/fuel_gauge.h>
#include <power/pmic.h>
#include <power/rk8xx_pmic.h>
#include <power/regulator.h>
#include <video_rockchip.h>
#ifdef CONFIG_IRQ
#include <irq-generic.h>
#include <rk_timer_irq.h>
#endif
#ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
#include <rk_eink.h>
#endif
DECLARE_GLOBAL_DATA_PTR;
#define IMAGE_RECALC_IDX -1
#define IMAGE_SOC_100_IDX(n) ((n) - 2)
#define IMAGE_LOWPOWER_IDX(n) ((n) - 1)
#define SYSTEM_SUSPEND_DELAY_MS 5000
#define FUEL_GAUGE_POLL_MS 1000
#define LED_CHARGING_NAME "battery_charging"
#define LED_CHARGING_FULL_NAME "battery_full"
struct charge_image {
const char *name;
int soc;
int period; /* ms */
};
struct charge_animation_priv {
struct udevice *pmic;
struct udevice *fg;
struct udevice *charger;
struct udevice *rtc;
#ifdef CONFIG_LED
struct udevice *led_charging;
struct udevice *led_full;
#endif
const struct charge_image *image;
int image_num;
int auto_wakeup_key_state;
ulong auto_screen_off_timeout; /* ms */
ulong suspend_delay_timeout; /* ms */
};
/*
* IF you want to use your own charge images, please:
*
* 1. Update the following 'image[]' to point to your own images;
* 2. You must set the failed image as last one and soc = -1 !!!
*/
static const struct charge_image image[] = {
{ .name = "battery_0.bmp", .soc = 5, .period = 600 },
{ .name = "battery_1.bmp", .soc = 20, .period = 600 },
{ .name = "battery_2.bmp", .soc = 40, .period = 600 },
{ .name = "battery_3.bmp", .soc = 60, .period = 600 },
{ .name = "battery_4.bmp", .soc = 80, .period = 600 },
{ .name = "battery_5.bmp", .soc = 100, .period = 600 },
{ .name = "battery_fail.bmp", .soc = -1, .period = 1000 },
};
static int regulators_parse_assigned_mem_state(struct udevice *dev)
{
struct charge_animation_pdata *pdata = dev_get_platdata(dev);
struct regulator_mem *mem;
const fdt32_t *list1;
const fdt32_t *list2;
int size1, size2;
int i, ret;
uint32_t phandle;
/* Must be both exist or not */
list1 = dev_read_prop(dev, "regulator-on-in-mem", &size1);
list2 = dev_read_prop(dev, "regulator-off-in-mem", &size2);
if (!list1 && !list2)
return 0;
if (list1 && !list2)
return -EINVAL;
else if (!list1 && list2)
return -EINVAL;
size1 = size1 / sizeof(*list1);
size2 = size2 / sizeof(*list2);
pdata->regulators_mem =
calloc(size1 + size2, sizeof(*pdata->regulators_mem));
if (!pdata->regulators_mem)
return -ENOMEM;
mem = pdata->regulators_mem;
for (i = 0; i < size1; i++, mem++) {
mem->enable = true;
phandle = fdt32_to_cpu(*list1++);
ret = uclass_get_device_by_phandle_id(UCLASS_REGULATOR,
phandle, &mem->dev);
if (ret)
return ret;
}
for (i = 0; i < size2; i++, mem++) {
mem->enable = false;
phandle = fdt32_to_cpu(*list2++);
ret = uclass_get_device_by_phandle_id(UCLASS_REGULATOR,
phandle, &mem->dev);
if (ret)
return ret;
}
#ifdef DEBUG
printf("assigned regulator mem:\n");
for (mem = pdata->regulators_mem; mem->dev; mem++)
printf(" %20s: suspend %s\n", mem->dev->name,
mem->enable ? "enabling" : "disabled");
#endif
return 0;
}
static int regulators_enable_assigned_state_mem(struct udevice *dev)
{
struct charge_animation_pdata *pdata = dev_get_platdata(dev);
struct regulator_mem *mem;
int ret;
for (mem = pdata->regulators_mem; mem->dev; mem++) {
ret = regulator_set_suspend_enable(mem->dev, mem->enable);
if (ret)
printf("%s: suspend failed, ret=%d\n",
mem->dev->name, ret);
}
return 0;
}
static void regulators_suspend(struct udevice *dev)
{
struct charge_animation_pdata *pdata = dev_get_platdata(dev);
if (pdata->regulators_mem)
regulators_enable_assigned_state_mem(dev);
else
regulators_enable_state_mem(false);
}
static void pmics_ops(bool suspend)
{
struct udevice *dev;
for (uclass_first_device(UCLASS_PMIC, &dev);
dev;
uclass_next_device(&dev)) {
if (suspend)
pmic_suspend(dev);
else
pmic_resume(dev);
}
}
static void pmics_suspend(void)
{
pmics_ops(true);
}
static void pmics_resume(void)
{
pmics_ops(false);
}
static int charge_animation_ofdata_to_platdata(struct udevice *dev)
{
struct charge_animation_pdata *pdata = dev_get_platdata(dev);
/* charge mode */
pdata->uboot_charge =
dev_read_u32_default(dev, "rockchip,uboot-charge-on", 0);
pdata->android_charge =
dev_read_u32_default(dev, "rockchip,android-charge-on", 0);
pdata->auto_exit_charge =
dev_read_u32_default(dev, "rockchip,uboot-exit-charge-auto", 0);
pdata->exit_charge_level =
dev_read_u32_default(dev, "rockchip,uboot-exit-charge-level", 0);
pdata->exit_charge_voltage =
dev_read_u32_default(dev, "rockchip,uboot-exit-charge-voltage", 0);
pdata->low_power_voltage =
dev_read_u32_default(dev, "rockchip,uboot-low-power-voltage", 0);
pdata->screen_on_voltage =
dev_read_u32_default(dev, "rockchip,screen-on-voltage", 0);
pdata->system_suspend =
dev_read_u32_default(dev, "rockchip,system-suspend", 0);
pdata->auto_wakeup_interval =
dev_read_u32_default(dev, "rockchip,auto-wakeup-interval", 0);
pdata->auto_wakeup_screen_invert =
dev_read_u32_default(dev, "rockchip,auto-wakeup-screen-invert", 0);
pdata->auto_off_screen_interval =
dev_read_u32_default(dev, "rockchip,auto-off-screen-interval", 15);
if (pdata->screen_on_voltage > pdata->exit_charge_voltage)
pdata->screen_on_voltage = pdata->exit_charge_voltage;
if (pdata->auto_exit_charge && !pdata->auto_wakeup_interval)
pdata->auto_wakeup_interval = 10;
/* Not allow failure */
if (regulators_parse_assigned_mem_state(dev)) {
printf("Failed to parse assigned mem state\n");
return -EINVAL;
}
debug("mode: uboot=%d, android=%d; exit: soc=%d%%, voltage=%dmv;\n"
"lp_voltage=%d%%, screen_on=%dmv\n",
pdata->uboot_charge, pdata->android_charge,
pdata->exit_charge_level, pdata->exit_charge_voltage,
pdata->low_power_voltage, pdata->screen_on_voltage);
return 0;
}
static int check_key_press(struct udevice *dev)
{
struct charge_animation_pdata *pdata = dev_get_platdata(dev);
struct charge_animation_priv *priv = dev_get_priv(dev);
u32 event;
#ifdef CONFIG_DM_RTC
if (priv->rtc && rtc_alarm_trigger(priv->rtc)) {
printf("rtc alarm trigger...\n");
return KEY_PRESS_LONG_DOWN;
}
#endif
event = key_read(KEY_POWER);
if (event < 0)
printf("read power key failed: %d\n", event);
else if (event == KEY_PRESS_DOWN)
printf("power key pressed...\n");
else if (event == KEY_PRESS_LONG_DOWN)
printf("power key long pressed...\n");
/* auto screen invert ? */
if (pdata->auto_wakeup_interval &&
pdata->auto_wakeup_screen_invert) {
if (priv->auto_wakeup_key_state == KEY_PRESS_DOWN) {
/* Value is updated in timer interrupt */
priv->auto_wakeup_key_state = KEY_PRESS_NONE;
event = KEY_PRESS_DOWN;
}
}
/* auto screen off (while not enable auto screen invert) ? */
if (!pdata->auto_wakeup_screen_invert &&
pdata->auto_off_screen_interval) {
if (priv->auto_screen_off_timeout &&
get_timer(priv->auto_screen_off_timeout) >
pdata->auto_off_screen_interval * 1000) { /* 1000ms */
event = KEY_PRESS_DOWN;
printf("Auto screen off\n");
}
}
return event;
}
/*
* If not enable CONFIG_IRQ, cpu can't suspend to ATF or wfi, so that wakeup
* period timer is useless.
*/
#if !defined(CONFIG_IRQ) || !defined(CONFIG_ARM_CPU_SUSPEND)
static int system_suspend_enter(struct udevice *dev)
{
return 0;
}
static void autowakeup_timer_init(struct udevice *dev, uint32_t seconds) {}
static void autowakeup_timer_uninit(void) {}
#else
static int system_suspend_enter(struct udevice *dev)
{
struct charge_animation_pdata *pdata = dev_get_platdata(dev);
struct charge_animation_priv *priv = dev_get_priv(dev);
/*
* When cpu is in wfi and we try to give a long key press event without
* key release, cpu would wakeup and enter wfi again immediately. So
* here is the problem: cpu can only wakeup when long key released.
*
* Actually, we want cpu can detect long key event without key release,
* so we give a suspend delay timeout for cpu to detect this.
*/
if (priv->suspend_delay_timeout &&
get_timer(priv->suspend_delay_timeout) <= SYSTEM_SUSPEND_DELAY_MS)
return 0;
if (pdata->system_suspend && IS_ENABLED(CONFIG_ARM_SMCCC)) {
printf("\nSystem suspend: ");
putc('0');
local_irq_disable();
putc('1');
regulators_suspend(dev);
putc('2');
pmics_suspend();
putc('3');
irqs_suspend();
putc('4');
device_suspend();
putc('5');
putc('\n');
/* Trap into ATF for low power mode */
cpu_suspend(0, psci_system_suspend);
putc('\n');
putc('4');
device_resume();
putc('3');
irqs_resume();
putc('2');
pmics_resume();
putc('1');
local_irq_enable();
putc('0');
putc('\n');
} else {
irqs_suspend();
printf("\nWfi\n");
wfi();
putc('1');
irqs_resume();
}
priv->suspend_delay_timeout = get_timer(0);
/*
* We must wait for key release event finish, otherwise
* we may read key state too early.
*/
mdelay(300);
return 0;
}
static void autowake_timer_handler(int irq, void *data)
{
struct udevice *dev = data;
struct charge_animation_priv *priv = dev_get_priv(dev);
static long long count;
writel(TIMER_CLR_INT, TIMER_BASE + TIMER_INTSTATUS);
priv->auto_wakeup_key_state = KEY_PRESS_DOWN;
printf("auto wakeup count: %lld\n", ++count);
}
static void autowakeup_timer_init(struct udevice *dev, uint32_t seconds)
{
uint64_t period = 24000000ULL * seconds;
/* Disable before conifg */
writel(0, TIMER_BASE + TIMER_CTRL);
/* Config */
writel((uint32_t)period, TIMER_BASE + TIMER_LOAD_COUNT0);
writel((uint32_t)(period >> 32), TIMER_BASE + TIMER_LOAD_COUNT1);
writel(TIMER_CLR_INT, TIMER_BASE + TIMER_INTSTATUS);
writel(TIMER_EN | TIMER_INT_EN, TIMER_BASE + TIMER_CTRL);
/* IRQ */
irq_install_handler(TIMER_IRQ, autowake_timer_handler, dev);
irq_handler_enable(TIMER_IRQ);
}
static void autowakeup_timer_uninit(void)
{
writel(0, TIMER_BASE + TIMER_CTRL);
irq_handler_disable(TIMER_IRQ);
irq_free_handler(TIMER_IRQ);
}
#endif
#ifdef CONFIG_DRM_ROCKCHIP
static void charge_show_bmp(const char *name)
{
rockchip_show_bmp(name);
}
static void charge_show_logo(void)
{
rockchip_show_logo();
}
#else
static void charge_show_bmp(const char *name) {}
static void charge_show_logo(void) {}
#endif
#ifdef CONFIG_LED
static int leds_update(struct udevice *dev, int soc)
{
struct charge_animation_priv *priv = dev_get_priv(dev);
static int old_soc = -1;
int ret, ledst;
if (old_soc == soc)
return 0;
old_soc = soc;
if (priv->led_charging) {
ledst = (soc < 100) ? LEDST_ON : LEDST_OFF;
ret = led_set_state(priv->led_charging, ledst);
if (ret) {
printf("set charging led %s failed, ret=%d\n",
(ledst == LEDST_ON) ? "ON" : "OFF", ret);
return ret;
}
}
if (priv->led_full) {
ledst = (soc == 100) ? LEDST_ON : LEDST_OFF;
ret = led_set_state(priv->led_full, ledst);
if (ret) {
printf("set charging full led %s failed, ret=%d\n",
ledst == LEDST_ON ? "ON" : "OFF", ret);
return ret;
}
}
return 0;
}
#else
static int leds_update(struct udevice *dev, int soc) { return 0; }
#endif
static int fg_charger_get_chrg_online(struct udevice *dev)
{
struct charge_animation_priv *priv = dev_get_priv(dev);
struct udevice *charger;
charger = priv->charger ? : priv->fg;
return fuel_gauge_get_chrg_online(charger);
}
static int sys_shutdown(struct udevice *dev)
{
struct charge_animation_priv *priv = dev_get_priv(dev);
struct udevice *pmic = priv->pmic;
struct udevice *fg = priv->fg;
/*
* Call the fuel/charge again to update something specific
* before shutdown. This fix a scene:
*
* Plug out charger which auto wakeup cpu from a long time system suspend,
* fuel/charge need to update something before shutdown.
*/
fg_charger_get_chrg_online(dev);
fuel_gauge_get_voltage(fg);
fuel_gauge_update_get_soc(fg);
flushc();
mdelay(50);
pmic_shutdown(pmic);
mdelay(500);
printf("Cpu should never reach here, shutdown failed !\n");
return 0;
}
static int charge_extrem_low_power(struct udevice *dev)
{
struct charge_animation_pdata *pdata = dev_get_platdata(dev);
struct charge_animation_priv *priv = dev_get_priv(dev);
struct udevice *fg = priv->fg;
int voltage, soc, charging = 1;
static int timer_initialized;
voltage = fuel_gauge_get_voltage(fg);
if (voltage < 0)
return -EINVAL;
while (voltage < pdata->low_power_voltage + 50) {
/* Check charger online */
charging = fg_charger_get_chrg_online(dev);
if (charging <= 0) {
printf("%s: Not charging, online=%d. Shutdown...\n",
__func__, charging);
sys_shutdown(dev);
continue;
}
/* Enable auto wakeup */
if (!timer_initialized) {
timer_initialized = 1;
autowakeup_timer_init(dev, 5);
}
/*
* Just for fuel gauge to update something important,
* including charge current, coulometer or other.
*/
soc = fuel_gauge_update_get_soc(fg);
if (soc < 0 || soc > 100) {
printf("get soc failed: %d\n", soc);
continue;
}
/* Update led */
leds_update(dev, soc);
printf("Extrem low power, force charging... threshold=%dmv, now=%dmv\n",
pdata->low_power_voltage, voltage);
/* System suspend */
system_suspend_enter(dev);
/* Update voltage */
voltage = fuel_gauge_get_voltage(fg);
if (voltage < 0) {
printf("get voltage failed: %d\n", voltage);
continue;
}
if (ctrlc()) {
printf("Extrem low charge: exit by ctrl+c\n");
break;
}
}
autowakeup_timer_uninit();
return 0;
}
static int charge_animation_show(struct udevice *dev)
{
struct charge_animation_pdata *pdata = dev_get_platdata(dev);
struct charge_animation_priv *priv = dev_get_priv(dev);
const struct charge_image *image = priv->image;
struct udevice *fg = priv->fg;
const char *preboot = env_get("preboot");
int image_num = priv->image_num;
bool ever_lowpower_screen_off = false;
bool screen_on = true;
ulong show_start = 0, charge_start = 0, debug_start = 0;
ulong delta;
ulong ms = 0, sec = 0;
int start_idx = 0, show_idx = -1, old_show_idx = IMAGE_RECALC_IDX;
int soc, voltage, current, key_state;
int i, charging = 1, ret;
int boot_mode;
int first_poll_fg = 1;
bool lp_shutdown = false;
/*
* Check sequence:
*
* 1. Extrem low power charge?
* 2. Preboot cmd?
* 3. Valid boot mode?
* 4. U-Boot charge enabled by dts config?
* 5. Screen off before charge?
* 6. Enter charge !
*
*/
if (!fuel_gauge_bat_is_exist(fg)) {
printf("Exit charge: battery is not exist\n");
return 0;
}
/* Extrem low power charge */
ret = charge_extrem_low_power(dev);
if (ret < 0) {
printf("extrem low power charge failed, ret=%d\n", ret);
return ret;
}
/* If there is preboot command, exit */
if (preboot && !strstr(preboot, "dvfs")) {
printf("Exit charge: due to preboot cmd '%s'\n", preboot);
return 0;
}
/* Not valid charge mode, exit */
#ifdef CONFIG_RKIMG_BOOTLOADER
boot_mode = rockchip_get_boot_mode();
if ((boot_mode != BOOT_MODE_CHARGING) &&
(boot_mode != BOOT_MODE_UNDEFINE)) {
printf("Exit charge: due to boot mode\n");
return 0;
}
#endif
/* No charger online + low power? shutdown */
charging = fg_charger_get_chrg_online(dev);
if (charging <= 0 && pdata->auto_exit_charge) {
soc = fuel_gauge_update_get_soc(fg);
voltage = fuel_gauge_get_voltage(fg);
if (soc < pdata->exit_charge_level) {
printf("soc(%d%%) < exit_charge_level(%d%%)\n",
soc, pdata->exit_charge_level);
lp_shutdown = true;
}
if (voltage < pdata->exit_charge_voltage) {
printf("voltage(%d) < exit_charge_voltage(%d)\n",
voltage, pdata->exit_charge_voltage);
lp_shutdown = true;
}
if (lp_shutdown) {
printf("Not charging and low power, Shutdown...\n");
show_idx = IMAGE_LOWPOWER_IDX(image_num);
charge_show_bmp(image[show_idx].name);
sys_shutdown(dev);
}
}
/* No charger online, exit */
if (charging <= 0) {
printf("Exit charge: due to charger offline\n");
return 0;
}
/* Enter android charge, set property for kernel */
if (pdata->android_charge) {
env_update("bootargs", "androidboot.mode=charger");
printf("Android charge mode\n");
}
/* Not enable U-Boot charge, exit */
if (!pdata->uboot_charge) {
printf("Exit charge: due to not enable uboot charge\n");
return 0;
}
voltage = fuel_gauge_get_voltage(fg);
if (voltage < 0) {
printf("get voltage failed: %d\n", voltage);
return -EINVAL;
}
/* If low power, turn off screen */
if (voltage <= pdata->screen_on_voltage + 50) {
screen_on = false;
ever_lowpower_screen_off = true;
charge_show_bmp(NULL);
}
/* Auto wakeup */
if (pdata->auto_wakeup_interval) {
printf("Auto wakeup: %dS\n", pdata->auto_wakeup_interval);
autowakeup_timer_init(dev, pdata->auto_wakeup_interval);
}
/* Give a message warning when CONFIG_IRQ is not enabled */
#ifdef CONFIG_IRQ
printf("Enter U-Boot charging mode\n");
#else
printf("Enter U-Boot charging mode(IRQ)\n");
#endif
charge_start = get_timer(0);
delta = get_timer(0);
/* Charging ! */
while (1) {
/*
* At the most time, fuel gauge is usually a i2c device, we
* should avoid read/write all the time. We had better set
* poll seconds to update fuel gauge info.
*/
if (!first_poll_fg && get_timer(delta) < FUEL_GAUGE_POLL_MS)
goto show_images;
delta = get_timer(0);
debug("step1 (%d)... \n", screen_on);
/*
* Most fuel gauge is I2C interface, it shouldn't be interrupted
* during transfer. The power key event depends on interrupt, so
* we should disable local irq when update fuel gauge.
*/
local_irq_disable();
/* Step1: Is charging now ? */
charging = fg_charger_get_chrg_online(dev);
if (charging <= 0) {
printf("Not charging, online=%d. Shutdown...\n",
charging);
#ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
/*
* If charger is plug out during charging, display poweroff
* image before device power off.
* Irq must be enable if CONFIG_IRQ is defined, because
* ebc need to wait irq to indicate frame is complete.
*/
local_irq_enable();
ret = rockchip_eink_show_charge_logo(EINK_LOGO_POWEROFF);
if (ret != 0)
printf("Eink display reset logo failed\n");
local_irq_disable();
#endif
sys_shutdown(dev);
continue;
}
debug("step2 (%d)... show_idx=%d\n", screen_on, show_idx);
/* Step2: get soc and voltage */
soc = fuel_gauge_update_get_soc(fg);
if (soc < 0 || soc > 100) {
printf("get soc failed: %d\n", soc);
continue;
}
voltage = fuel_gauge_get_voltage(fg);
if (voltage < 0) {
printf("get voltage failed: %d\n", voltage);
continue;
}
current = fuel_gauge_get_current(fg);
if (current == -ENOSYS) {
printf("get current failed: %d\n", current);
continue;
}
first_poll_fg = 0;
local_irq_enable();
if (pdata->auto_exit_charge) {
/* Is able to boot now ? */
if (pdata->exit_charge_level &&
soc >= pdata->exit_charge_level) {
printf("soc(%d%%) exit charge animation...\n",
soc);
break;
}
if (pdata->exit_charge_voltage &&
voltage >= pdata->exit_charge_voltage) {
printf("vol(%d) exit charge animation...\n",
voltage);
break;
}
}
show_images:
/*
* Just for debug, otherwise there will be nothing output which
* is not good to know what happen.
*/
if (!debug_start)
debug_start = get_timer(0);
if (get_timer(debug_start) > 30000) {
debug_start = get_timer(0);
printf("[%8ld]: soc=%d%%, vol=%dmv, c=%dma, "
"online=%d, screen_on=%d\n",
get_timer(0) / 1000, soc, voltage,
current, charging, screen_on);
}
/* Update leds */
leds_update(dev, soc);
/*
* If ever lowpower screen off, force screen_on=false, which
* means key event can't modify screen_on, only voltage higher
* then threshold can update screen_on=true;
*/
if (ever_lowpower_screen_off)
screen_on = false;
/*
* Auto turn on screen when voltage higher than Vol screen on.
* 'ever_lowpower_screen_off' means enter the while(1) loop with
* screen off.
*/
if ((ever_lowpower_screen_off) &&
(voltage > pdata->screen_on_voltage)) {
ever_lowpower_screen_off = false;
screen_on = true;
show_idx = IMAGE_RECALC_IDX;
}
/*
* IMAGE_RECALC_IDX means show_idx show be update by start_idx.
* When short key pressed event trigged, we will set show_idx
* as IMAGE_RECALC_IDX which updates images index from start_idx
* that calculate by current soc.
*/
if (show_idx == IMAGE_RECALC_IDX) {
for (i = 0; i < IMAGE_SOC_100_IDX(image_num); i++) {
/* Find out which image we start to show */
if ((soc >= image[i].soc) && (soc < image[i + 1].soc)) {
start_idx = i;
break;
}
if (soc >= 100) {
start_idx = IMAGE_SOC_100_IDX(image_num);
break;
}
}
debug("%s: show_idx=%d, screen_on=%d\n",
__func__, show_idx, screen_on);
/* Mark start index and start time */
show_idx = start_idx;
show_start = get_timer(0);
}
debug("step3 (%d)... show_idx=%d\n", screen_on, show_idx);
#ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
/*
* Device is auto wakeup from suspend, if it's eink display,
* screen will display the last image after suspend, so
* we should update the image to show the approximate
* battery power if battery is charging to next level.
*/
if (pdata->auto_wakeup_interval &&
priv->auto_wakeup_key_state == KEY_PRESS_DOWN &&
!screen_on) {
if (soc >= image[old_show_idx + 1].soc &&
soc < 100) {
int ret;
int logo_type = EINK_LOGO_CHARGING_0;
logo_type = logo_type << (old_show_idx + 1);
ret = rockchip_eink_show_charge_logo(logo_type);
/*
* only change the logic if eink is
* actually exist
*/
if (ret == 0) {
printf("Update image id[%d] for eink\n",
old_show_idx + 1);
old_show_idx++;
}
}
}
/*
* If battery capacity is charged to 100%, exit charging
* animation and boot android system.
*/
if (soc >= 100) {
int ret;
int logo_type = EINK_LOGO_CHARGING_5;
ret = rockchip_eink_show_charge_logo(logo_type);
/* Only change the logic if eink is acutally exist */
if (ret == 0) {
printf("battery FULL,exit charge animation\n");
mdelay(20);
break;
}
}
#endif
/* Step3: show images */
if (screen_on) {
/* Don't call 'charge_show_bmp' unless image changed */
if (old_show_idx != show_idx) {
#ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
int logo_type = EINK_LOGO_CHARGING_0;
rockchip_eink_show_charge_logo(logo_type <<
show_idx);
#endif
old_show_idx = show_idx;
debug("SHOW: %s\n", image[show_idx].name);
charge_show_bmp(image[show_idx].name);
}
/* Re-calculate timeout to off screen */
if (priv->auto_screen_off_timeout == 0)
priv->auto_screen_off_timeout = get_timer(0);
} else {
/* Entering low power suspend mode !!! */
priv->auto_screen_off_timeout = 0;
system_suspend_enter(dev);
}
mdelay(5);
/* It's time to show next image ? */
if (get_timer(show_start) > image[show_idx].period) {
show_start = get_timer(0);
show_idx++;
if (show_idx > IMAGE_SOC_100_IDX(image_num))
show_idx = IMAGE_RECALC_IDX;
}
debug("step4 (%d)... \n", screen_on);
/*
* Step4: check key event.
*
* Short key event: turn on/off screen;
* Long key event: show logo and boot system or still charging.
*/
key_state = check_key_press(dev);
if (key_state == KEY_PRESS_DOWN) {
/* Clear current image index, recalc image index */
old_show_idx = IMAGE_RECALC_IDX;
show_idx = IMAGE_RECALC_IDX;
/*
* Reverse the screen state
*
* If screen_on=false, means this short key pressed
* event turn on the screen and we need show images.
*
* If screen_on=true, means this short key pressed
* event turn off the screen and we never show images.
*/
if (screen_on) {
#ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
int type = EINK_LOGO_CHARGING_0 << start_idx;
/*
* Show current battery capacity before suspend
* if it's eink display, because eink screen
* will continue to display the last image
* after suspend, so user can get the
* approximate capacity by image displayed.
*/
ret = rockchip_eink_show_charge_logo(type);
/* only change the logic if eink display ok */
if (ret == 0)
old_show_idx = start_idx;
#endif
charge_show_bmp(NULL); /* Turn off screen */
screen_on = false;
priv->suspend_delay_timeout = get_timer(0);
} else {
screen_on = true;
}
printf("screen %s\n", screen_on ? "on" : "off");
} else if (key_state == KEY_PRESS_LONG_DOWN) {
/* Set screen_on=true anyway when key long pressed */
if (!screen_on)
screen_on = true;
printf("screen %s\n", screen_on ? "on" : "off");
/* Is able to boot now ? */
if (soc < pdata->exit_charge_level) {
printf("soc=%d%%, threshold soc=%d%%\n",
soc, pdata->exit_charge_level);
printf("Low power, unable to boot, charging...\n");
show_idx = IMAGE_LOWPOWER_IDX(image_num);
continue;
}
if (voltage < pdata->exit_charge_voltage) {
printf("voltage=%dmv, threshold voltage=%dmv\n",
voltage, pdata->exit_charge_voltage);
printf("Low power, unable to boot, charging...\n");
show_idx = IMAGE_LOWPOWER_IDX(image_num);
continue;
}
/* Success exit charging */
printf("Exit charge animation...\n");
charge_show_logo();
break;
} else {
/* Do nothing */
}
debug("step5 (%d)... \n", screen_on);
/* Step5: Exit by ctrl+c */
if (ctrlc()) {
if (voltage >= pdata->screen_on_voltage)
charge_show_logo();
printf("Exit charge, due to ctrl+c\n");
break;
}
}
if (pdata->auto_wakeup_interval)
autowakeup_timer_uninit();
ms = get_timer(charge_start);
if (ms >= 1000) {
sec = ms / 1000;
ms = ms % 1000;
}
printf("charging time total: %lu.%lus, soc=%d%%, vol=%dmv\n",
sec, ms, soc, voltage);
return 0;
}
static int fg_charger_get_device(struct udevice **fuel_gauge,
struct udevice **charger)
{
struct udevice *dev;
struct uclass *uc;
int ret, cap;
*fuel_gauge = NULL,
*charger = NULL;
ret = uclass_get(UCLASS_FG, &uc);
if (ret)
return ret;
for (uclass_first_device(UCLASS_FG, &dev);
dev;
uclass_next_device(&dev)) {
cap = fuel_gauge_capability(dev);
if (cap == (FG_CAP_CHARGER | FG_CAP_FUEL_GAUGE)) {
*fuel_gauge = dev;
*charger = NULL;
} else if (cap == FG_CAP_FUEL_GAUGE) {
*fuel_gauge = dev;
} else if (cap == FG_CAP_CHARGER) {
*charger = dev;
}
}
return (*fuel_gauge) ? 0 : -ENODEV;
}
static const struct dm_charge_display_ops charge_animation_ops = {
.show = charge_animation_show,
};
static int charge_animation_probe(struct udevice *dev)
{
struct charge_animation_priv *priv = dev_get_priv(dev);
int ret, soc;
/* Get PMIC: used for power off system */
ret = uclass_get_device(UCLASS_PMIC, 0, &priv->pmic);
if (ret) {
if (ret == -ENODEV)
printf("Can't find PMIC\n");
else
printf("Get UCLASS PMIC failed: %d\n", ret);
return ret;
}
/* Get fuel gauge and charger(If need) */
ret = fg_charger_get_device(&priv->fg, &priv->charger);
if (ret) {
if (ret == -ENODEV)
debug("Can't find FG\n");
else
debug("Get UCLASS FG failed: %d\n", ret);
return ret;
}
/* Get rtc: used for power on */
ret = uclass_get_device(UCLASS_RTC, 0, &priv->rtc);
if (ret) {
if (ret == -ENODEV)
debug("Can't find RTC\n");
else
debug("Get UCLASS RTC failed: %d\n", ret);
}
/* Get PWRKEY: used for wakeup and turn off/on LCD */
if (!key_exist(KEY_POWER)) {
debug("Can't find power key\n");
return -EINVAL;
}
/* Initialize charge current */
soc = fuel_gauge_update_get_soc(priv->fg);
if (soc < 0 || soc > 100) {
debug("get soc failed: %d\n", soc);
return -EINVAL;
}
/* Get leds */
#ifdef CONFIG_LED
ret = led_get_by_label(LED_CHARGING_NAME, &priv->led_charging);
if (!ret)
printf("Found Charging LED\n");
ret = led_get_by_label(LED_CHARGING_FULL_NAME, &priv->led_full);
if (!ret)
printf("Found Charging-Full LED\n");
#endif
/* Get charge images */
priv->image = image;
priv->image_num = ARRAY_SIZE(image);
printf("Enable charge animation display\n");
return 0;
}
static const struct udevice_id charge_animation_ids[] = {
{ .compatible = "rockchip,uboot-charge" },
{ },
};
U_BOOT_DRIVER(charge_animation) = {
.name = "charge-animation",
.id = UCLASS_CHARGE_DISPLAY,
.probe = charge_animation_probe,
.of_match = charge_animation_ids,
.ops = &charge_animation_ops,
.ofdata_to_platdata = charge_animation_ofdata_to_platdata,
.platdata_auto_alloc_size = sizeof(struct charge_animation_pdata),
.priv_auto_alloc_size = sizeof(struct charge_animation_priv),
};