xref: /rk3399_rockchip-uboot/drivers/power/charge_animation.c (revision 77bac292f4ebd0ec3e4e2e49c2af5551cbc57f2d)
1f1c8ecceSJoseph Chen /*
2f1c8ecceSJoseph Chen  * (C) Copyright 2017 Rockchip Electronics Co., Ltd
3f1c8ecceSJoseph Chen  *
4f1c8ecceSJoseph Chen  * SPDX-License-Identifier:     GPL-2.0+
5f1c8ecceSJoseph Chen  */
6f1c8ecceSJoseph Chen 
7e7f9facbSJoseph Chen #include <asm/io.h>
8f1c8ecceSJoseph Chen #include <common.h>
9d3ff9cf9SKever Yang #include <boot_rkimg.h>
101367bfe3SJoseph Chen #include <console.h>
11f1c8ecceSJoseph Chen #include <dm.h>
12f1c8ecceSJoseph Chen #include <errno.h>
13f1c8ecceSJoseph Chen #include <key.h>
141b3009deSJoseph Chen #include <led.h>
158b436ce5SElaine Zhang #include <rtc.h>
16f1c8ecceSJoseph Chen #include <pwm.h>
17d3ff9cf9SKever Yang #include <asm/arch/rockchip_smccc.h>
18d3ff9cf9SKever Yang #include <asm/suspend.h>
19d3ff9cf9SKever Yang #include <linux/input.h>
20f1c8ecceSJoseph Chen #include <power/charge_display.h>
21ac1dc0c3SJoseph Chen #include <power/charge_animation.h>
2288949342SJoseph Chen #include <power/rockchip_pm.h>
23f1c8ecceSJoseph Chen #include <power/fuel_gauge.h>
24f1c8ecceSJoseph Chen #include <power/pmic.h>
25f1c8ecceSJoseph Chen #include <power/rk8xx_pmic.h>
26f1c8ecceSJoseph Chen #include <power/regulator.h>
27f1c8ecceSJoseph Chen #include <video_rockchip.h>
28175257e4SJoseph Chen #ifdef CONFIG_IRQ
29175257e4SJoseph Chen #include <irq-generic.h>
30175257e4SJoseph Chen #include <rk_timer_irq.h>
31175257e4SJoseph Chen #endif
32255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
33255e5751SWenping Zhang #include <rk_eink.h>
34255e5751SWenping Zhang #endif
35f1c8ecceSJoseph Chen DECLARE_GLOBAL_DATA_PTR;
36f1c8ecceSJoseph Chen 
37ebe3d004SJoseph Chen #define IMAGE_RESET_IDX				-1
38ebe3d004SJoseph Chen #define IMAGE_SOC_100_IDX(n)			((n) - 2)
39ebe3d004SJoseph Chen #define IMAGE_LOWPOWER_IDX(n)			((n) - 1)
402e68f6b5SJoseph Chen #define SYSTEM_SUSPEND_DELAY_MS			5000
415e804741SJoseph Chen #define FUEL_GAUGE_POLL_MS			1000
42f1c8ecceSJoseph Chen 
431b3009deSJoseph Chen #define LED_CHARGING_NAME			"battery_charging"
441b3009deSJoseph Chen #define LED_CHARGING_FULL_NAME			"battery_full"
451b3009deSJoseph Chen 
46f1c8ecceSJoseph Chen struct charge_image {
47f1c8ecceSJoseph Chen 	const char *name;
48f1c8ecceSJoseph Chen 	int soc;
49f1c8ecceSJoseph Chen 	int period;	/* ms */
50f1c8ecceSJoseph Chen };
51f1c8ecceSJoseph Chen 
52f1c8ecceSJoseph Chen struct charge_animation_priv {
53f1c8ecceSJoseph Chen 	struct udevice *pmic;
54f1c8ecceSJoseph Chen 	struct udevice *fg;
554d083e3fSJoseph Chen 	struct udevice *charger;
568b436ce5SElaine Zhang 	struct udevice *rtc;
571b3009deSJoseph Chen #ifdef CONFIG_LED
581b3009deSJoseph Chen 	struct udevice *led_charging;
591b3009deSJoseph Chen 	struct udevice *led_full;
601b3009deSJoseph Chen #endif
61f1c8ecceSJoseph Chen 	const struct charge_image *image;
62f1c8ecceSJoseph Chen 	int image_num;
63e7f9facbSJoseph Chen 
64e7f9facbSJoseph Chen 	int auto_wakeup_key_state;
652e68f6b5SJoseph Chen 	ulong auto_screen_off_timeout;	/* ms */
662e68f6b5SJoseph Chen 	ulong suspend_delay_timeout;	/* ms */
67f1c8ecceSJoseph Chen };
68f1c8ecceSJoseph Chen 
69f1c8ecceSJoseph Chen /*
70f1c8ecceSJoseph Chen  * IF you want to use your own charge images, please:
71f1c8ecceSJoseph Chen  *
72f1c8ecceSJoseph Chen  * 1. Update the following 'image[]' to point to your own images;
73f1c8ecceSJoseph Chen  * 2. You must set the failed image as last one and soc = -1 !!!
74f1c8ecceSJoseph Chen  */
75f1c8ecceSJoseph Chen static const struct charge_image image[] = {
76f1c8ecceSJoseph Chen 	{ .name = "battery_0.bmp", .soc = 5, .period = 600 },
77f1c8ecceSJoseph Chen 	{ .name = "battery_1.bmp", .soc = 20, .period = 600 },
78f1c8ecceSJoseph Chen 	{ .name = "battery_2.bmp", .soc = 40, .period = 600 },
79f1c8ecceSJoseph Chen 	{ .name = "battery_3.bmp", .soc = 60, .period = 600 },
80f1c8ecceSJoseph Chen 	{ .name = "battery_4.bmp", .soc = 80, .period = 600 },
81f1c8ecceSJoseph Chen 	{ .name = "battery_5.bmp", .soc = 100, .period = 600 },
82f1c8ecceSJoseph Chen 	{ .name = "battery_fail.bmp", .soc = -1, .period = 1000 },
83f1c8ecceSJoseph Chen };
84f1c8ecceSJoseph Chen 
85f1c8ecceSJoseph Chen static int charge_animation_ofdata_to_platdata(struct udevice *dev)
86f1c8ecceSJoseph Chen {
87f1c8ecceSJoseph Chen 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
88f1c8ecceSJoseph Chen 
89d6653c12SJoseph Chen 	/* charge mode */
90d6653c12SJoseph Chen 	pdata->uboot_charge =
91d6653c12SJoseph Chen 		dev_read_u32_default(dev, "rockchip,uboot-charge-on", 0);
92d6653c12SJoseph Chen 	pdata->android_charge =
93d6653c12SJoseph Chen 		dev_read_u32_default(dev, "rockchip,android-charge-on", 0);
94f1c8ecceSJoseph Chen 
95038c1ecaSShunqing Chen 	pdata->auto_exit_charge =
96038c1ecaSShunqing Chen 		dev_read_u32_default(dev, "rockchip,uboot-exit-charge-auto", 0);
97d6653c12SJoseph Chen 	pdata->exit_charge_level =
98d6653c12SJoseph Chen 		dev_read_u32_default(dev, "rockchip,uboot-exit-charge-level", 0);
99d6653c12SJoseph Chen 	pdata->exit_charge_voltage =
100d6653c12SJoseph Chen 		dev_read_u32_default(dev, "rockchip,uboot-exit-charge-voltage", 0);
101f23c35a8SJoseph Chen 
102f23c35a8SJoseph Chen 	pdata->low_power_voltage =
103f23c35a8SJoseph Chen 		dev_read_u32_default(dev, "rockchip,uboot-low-power-voltage", 0);
104f23c35a8SJoseph Chen 
105d6653c12SJoseph Chen 	pdata->screen_on_voltage =
106d6653c12SJoseph Chen 		dev_read_u32_default(dev, "rockchip,screen-on-voltage", 0);
107f23c35a8SJoseph Chen 	pdata->system_suspend =
108f23c35a8SJoseph Chen 		dev_read_u32_default(dev, "rockchip,system-suspend", 0);
109f1c8ecceSJoseph Chen 
110e7f9facbSJoseph Chen 	pdata->auto_wakeup_interval =
111e7f9facbSJoseph Chen 		dev_read_u32_default(dev, "rockchip,auto-wakeup-interval", 0);
112e7f9facbSJoseph Chen 	pdata->auto_wakeup_screen_invert =
113e7f9facbSJoseph Chen 		dev_read_u32_default(dev, "rockchip,auto-wakeup-screen-invert", 0);
114e7f9facbSJoseph Chen 
115e7f9facbSJoseph Chen 	pdata->auto_off_screen_interval =
116e7f9facbSJoseph Chen 		dev_read_u32_default(dev, "rockchip,auto-off-screen-interval", 15);
117e7f9facbSJoseph Chen 
118f23c35a8SJoseph Chen 	if (pdata->screen_on_voltage > pdata->exit_charge_voltage)
119f23c35a8SJoseph Chen 		pdata->screen_on_voltage = pdata->exit_charge_voltage;
120f1c8ecceSJoseph Chen 
121038c1ecaSShunqing Chen 	if (pdata->auto_exit_charge && !pdata->auto_wakeup_interval)
122038c1ecaSShunqing Chen 		pdata->auto_wakeup_interval = 10;
123038c1ecaSShunqing Chen 
124d6653c12SJoseph Chen 	debug("mode: uboot=%d, android=%d; exit: soc=%d%%, voltage=%dmv;\n"
125f23c35a8SJoseph Chen 	      "lp_voltage=%d%%, screen_on=%dmv\n",
126d6653c12SJoseph Chen 	      pdata->uboot_charge, pdata->android_charge,
127d6653c12SJoseph Chen 	      pdata->exit_charge_level, pdata->exit_charge_voltage,
128f23c35a8SJoseph Chen 	      pdata->low_power_voltage, pdata->screen_on_voltage);
129f1c8ecceSJoseph Chen 
130f1c8ecceSJoseph Chen 	return 0;
131f1c8ecceSJoseph Chen }
132f1c8ecceSJoseph Chen 
133e7f9facbSJoseph Chen static int check_key_press(struct udevice *dev)
134f1c8ecceSJoseph Chen {
135e7f9facbSJoseph Chen 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
136e7f9facbSJoseph Chen 	struct charge_animation_priv *priv = dev_get_priv(dev);
1378b436ce5SElaine Zhang 	u32 state, rtc_state = 0;
1388b436ce5SElaine Zhang 
1398b436ce5SElaine Zhang #ifdef CONFIG_DM_RTC
1408b436ce5SElaine Zhang 	if (priv->rtc)
1418b436ce5SElaine Zhang 		rtc_state = rtc_alarm_trigger(priv->rtc);
1428b436ce5SElaine Zhang #endif
1438b436ce5SElaine Zhang 	if (rtc_state) {
1448b436ce5SElaine Zhang 		printf("rtc alarm trigger...\n");
1458b436ce5SElaine Zhang 		return KEY_PRESS_LONG_DOWN;
1468b436ce5SElaine Zhang 	}
147f1c8ecceSJoseph Chen 
148787a62ebSJoseph Chen 	state = key_read(KEY_POWER);
149b177a917SJoseph Chen 	if (state < 0)
150f1c8ecceSJoseph Chen 		printf("read power key failed: %d\n", state);
15193aee2d9SJoseph Chen 	else if (state == KEY_PRESS_DOWN)
15293aee2d9SJoseph Chen 		printf("power key pressed...\n");
15393aee2d9SJoseph Chen 	else if (state == KEY_PRESS_LONG_DOWN)
15493aee2d9SJoseph Chen 		printf("power key long pressed...\n");
155f1c8ecceSJoseph Chen 
156e7f9facbSJoseph Chen 	/* Fixup key state for following cases */
157e7f9facbSJoseph Chen 	if (pdata->auto_wakeup_interval) {
158e7f9facbSJoseph Chen 		if (pdata->auto_wakeup_screen_invert) {
159e7f9facbSJoseph Chen 			if (priv->auto_wakeup_key_state == KEY_PRESS_DOWN) {
160e7f9facbSJoseph Chen 				/* Value is updated in timer interrupt */
161e7f9facbSJoseph Chen 				priv->auto_wakeup_key_state = KEY_PRESS_NONE;
162e7f9facbSJoseph Chen 				state = KEY_PRESS_DOWN;
163e7f9facbSJoseph Chen 			}
164e7f9facbSJoseph Chen 		}
16523858492SShunqing Chen 	}
16623858492SShunqing Chen 	if (!pdata->auto_wakeup_screen_invert &&
16723858492SShunqing Chen 	    pdata->auto_off_screen_interval) {
16893aee2d9SJoseph Chen 		if (priv->auto_screen_off_timeout &&
16993aee2d9SJoseph Chen 		    get_timer(priv->auto_screen_off_timeout) >
170e7f9facbSJoseph Chen 		    pdata->auto_off_screen_interval * 1000) {	/* 1000ms */
171e7f9facbSJoseph Chen 			state = KEY_PRESS_DOWN;
172e7f9facbSJoseph Chen 			printf("Auto screen off\n");
173e7f9facbSJoseph Chen 		}
174e7f9facbSJoseph Chen 	}
175e7f9facbSJoseph Chen 
176b177a917SJoseph Chen 	return state;
177f1c8ecceSJoseph Chen }
178f1c8ecceSJoseph Chen 
179175257e4SJoseph Chen /*
180175257e4SJoseph Chen  * If not enable CONFIG_IRQ, cpu can't suspend to ATF or wfi, so that wakeup
181175257e4SJoseph Chen  * period timer is useless.
182175257e4SJoseph Chen  */
1833415d4ebSJoseph Chen #if !defined(CONFIG_IRQ) || !defined(CONFIG_ARM_CPU_SUSPEND)
1842e68f6b5SJoseph Chen static int system_suspend_enter(struct udevice *dev)
185175257e4SJoseph Chen {
186175257e4SJoseph Chen 	return 0;
187175257e4SJoseph Chen }
188175257e4SJoseph Chen 
189175257e4SJoseph Chen static void autowakeup_timer_init(struct udevice *dev, uint32_t seconds) {}
190175257e4SJoseph Chen static void autowakeup_timer_uninit(void) {}
191175257e4SJoseph Chen 
192175257e4SJoseph Chen #else
1932e68f6b5SJoseph Chen static int system_suspend_enter(struct udevice *dev)
194b177a917SJoseph Chen {
1952e68f6b5SJoseph Chen 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
1962e68f6b5SJoseph Chen 	struct charge_animation_priv *priv = dev_get_priv(dev);
1972e68f6b5SJoseph Chen 
1982e68f6b5SJoseph Chen 	/*
1992e68f6b5SJoseph Chen 	 * When cpu is in wfi and we try to give a long key press event without
2002e68f6b5SJoseph Chen 	 * key release, cpu would wakeup and enter wfi again immediately. So
2012e68f6b5SJoseph Chen 	 * here is the problem: cpu can only wakeup when long key released.
2022e68f6b5SJoseph Chen 	 *
2032e68f6b5SJoseph Chen 	 * Actually, we want cpu can detect long key event without key release,
2042e68f6b5SJoseph Chen 	 * so we give a suspend delay timeout for cpu to detect this.
2052e68f6b5SJoseph Chen 	 */
2062e68f6b5SJoseph Chen 	if (priv->suspend_delay_timeout &&
2072e68f6b5SJoseph Chen 	    get_timer(priv->suspend_delay_timeout) <= SYSTEM_SUSPEND_DELAY_MS)
2082e68f6b5SJoseph Chen 		return 0;
2092e68f6b5SJoseph Chen 
210f23c35a8SJoseph Chen 	if (pdata->system_suspend && IS_ENABLED(CONFIG_ARM_SMCCC)) {
211b177a917SJoseph Chen 		printf("\nSystem suspend: ");
212992f4e77SJoseph Chen 		putc('0');
213b177a917SJoseph Chen 		local_irq_disable();
21406b61291SJoseph Chen 		putc('1');
21506b61291SJoseph Chen 		regulators_enable_state_mem(false);
216b177a917SJoseph Chen 		putc('2');
21706b61291SJoseph Chen 		pmic_suspend(priv->pmic);
218b177a917SJoseph Chen 		putc('3');
21906b61291SJoseph Chen 		irqs_suspend();
22088949342SJoseph Chen 		putc('4');
22106b61291SJoseph Chen 		device_suspend();
22206b61291SJoseph Chen 		putc('5');
223b177a917SJoseph Chen 		putc('\n');
224b177a917SJoseph Chen 
225b177a917SJoseph Chen 		/* Trap into ATF for low power mode */
226b177a917SJoseph Chen 		cpu_suspend(0, psci_system_suspend);
227b177a917SJoseph Chen 
228b177a917SJoseph Chen 		putc('\n');
22988949342SJoseph Chen 		putc('4');
23088949342SJoseph Chen 		device_resume();
231b177a917SJoseph Chen 		putc('3');
232b177a917SJoseph Chen 		irqs_resume();
233b177a917SJoseph Chen 		putc('2');
23406b61291SJoseph Chen 		pmic_resume(priv->pmic);
235b177a917SJoseph Chen 		putc('1');
23606b61291SJoseph Chen 		local_irq_enable();
23706b61291SJoseph Chen 		putc('0');
238b177a917SJoseph Chen 		putc('\n');
2398fc5ae06SJoseph Chen 	} else {
2405eac14dbSJoseph Chen 		irqs_suspend();
2418fc5ae06SJoseph Chen 		printf("\nWfi\n");
2428fc5ae06SJoseph Chen 		wfi();
2432e68f6b5SJoseph Chen 		putc('1');
2445eac14dbSJoseph Chen 		irqs_resume();
2458fc5ae06SJoseph Chen 	}
246b177a917SJoseph Chen 
2472e68f6b5SJoseph Chen 	priv->suspend_delay_timeout = get_timer(0);
2482e68f6b5SJoseph Chen 
249b177a917SJoseph Chen 	/*
250b177a917SJoseph Chen 	 * We must wait for key release event finish, otherwise
251b177a917SJoseph Chen 	 * we may read key state too early.
252b177a917SJoseph Chen 	 */
253b177a917SJoseph Chen 	mdelay(300);
254b177a917SJoseph Chen 
255b177a917SJoseph Chen 	return 0;
256f1c8ecceSJoseph Chen }
257ebe3d004SJoseph Chen 
25879244e4cSJoseph Chen static void timer_irq_handler(int irq, void *data)
25979244e4cSJoseph Chen {
26079244e4cSJoseph Chen 	struct udevice *dev = data;
26179244e4cSJoseph Chen 	struct charge_animation_priv *priv = dev_get_priv(dev);
26279244e4cSJoseph Chen 	static long long count;
26379244e4cSJoseph Chen 
26479244e4cSJoseph Chen 	writel(TIMER_CLR_INT, TIMER_BASE + TIMER_INTSTATUS);
26579244e4cSJoseph Chen 
26679244e4cSJoseph Chen 	priv->auto_wakeup_key_state = KEY_PRESS_DOWN;
26779244e4cSJoseph Chen 	printf("auto wakeup count: %lld\n", ++count);
26879244e4cSJoseph Chen }
26979244e4cSJoseph Chen 
27079244e4cSJoseph Chen static void autowakeup_timer_init(struct udevice *dev, uint32_t seconds)
27179244e4cSJoseph Chen {
27279244e4cSJoseph Chen 	uint64_t period = 24000000ULL * seconds;
27379244e4cSJoseph Chen 
27479244e4cSJoseph Chen 	/* Disable before conifg */
27579244e4cSJoseph Chen 	writel(0, TIMER_BASE + TIMER_CTRL);
27679244e4cSJoseph Chen 
27779244e4cSJoseph Chen 	/* Config */
27879244e4cSJoseph Chen 	writel((uint32_t)period, TIMER_BASE + TIMER_LOAD_COUNT0);
27979244e4cSJoseph Chen 	writel((uint32_t)(period >> 32), TIMER_BASE + TIMER_LOAD_COUNT1);
28079244e4cSJoseph Chen 	writel(TIMER_CLR_INT, TIMER_BASE + TIMER_INTSTATUS);
28179244e4cSJoseph Chen 	writel(TIMER_EN | TIMER_INT_EN, TIMER_BASE + TIMER_CTRL);
28279244e4cSJoseph Chen 
28379244e4cSJoseph Chen 	/* IRQ */
28479244e4cSJoseph Chen 	irq_install_handler(TIMER_IRQ, timer_irq_handler, dev);
28579244e4cSJoseph Chen 	irq_handler_enable(TIMER_IRQ);
28679244e4cSJoseph Chen }
28779244e4cSJoseph Chen 
28879244e4cSJoseph Chen static void autowakeup_timer_uninit(void)
28979244e4cSJoseph Chen {
290094465a9SJoseph Chen 	writel(0, TIMER_BASE + TIMER_CTRL);
291094465a9SJoseph Chen 
292094465a9SJoseph Chen 	irq_handler_disable(TIMER_IRQ);
29379244e4cSJoseph Chen 	irq_free_handler(TIMER_IRQ);
29479244e4cSJoseph Chen }
295175257e4SJoseph Chen #endif
29679244e4cSJoseph Chen 
297f23c35a8SJoseph Chen #ifdef CONFIG_DRM_ROCKCHIP
298f23c35a8SJoseph Chen static void charge_show_bmp(const char *name)
299f23c35a8SJoseph Chen {
300f23c35a8SJoseph Chen 	rockchip_show_bmp(name);
301f23c35a8SJoseph Chen }
302f23c35a8SJoseph Chen 
303f23c35a8SJoseph Chen static void charge_show_logo(void)
304f23c35a8SJoseph Chen {
305f23c35a8SJoseph Chen 	rockchip_show_logo();
306f23c35a8SJoseph Chen }
307f23c35a8SJoseph Chen #else
308f23c35a8SJoseph Chen static void charge_show_bmp(const char *name) {}
309f23c35a8SJoseph Chen static void charge_show_logo(void) {}
310f23c35a8SJoseph Chen #endif
311f23c35a8SJoseph Chen 
3121b3009deSJoseph Chen #ifdef CONFIG_LED
3131b3009deSJoseph Chen static int leds_update(struct udevice *dev, int soc)
3141b3009deSJoseph Chen {
3151b3009deSJoseph Chen 	struct charge_animation_priv *priv = dev_get_priv(dev);
3161b3009deSJoseph Chen 	static int old_soc = -1;
3171b3009deSJoseph Chen 	int ret, ledst;
3181b3009deSJoseph Chen 
3191b3009deSJoseph Chen 	if (old_soc == soc)
3201b3009deSJoseph Chen 		return 0;
3211b3009deSJoseph Chen 
3221b3009deSJoseph Chen 	old_soc = soc;
3231b3009deSJoseph Chen 	if (priv->led_charging) {
3241b3009deSJoseph Chen 		ledst = (soc < 100) ? LEDST_ON : LEDST_OFF;
3251b3009deSJoseph Chen 		ret = led_set_state(priv->led_charging, ledst);
3261b3009deSJoseph Chen 		if (ret) {
3271b3009deSJoseph Chen 			printf("set charging led %s failed, ret=%d\n",
3281b3009deSJoseph Chen 			       (ledst == LEDST_ON) ? "ON" : "OFF", ret);
3291b3009deSJoseph Chen 			return ret;
3301b3009deSJoseph Chen 		}
3311b3009deSJoseph Chen 	}
3321b3009deSJoseph Chen 
3331b3009deSJoseph Chen 	if (priv->led_full) {
3341b3009deSJoseph Chen 		ledst = (soc == 100) ? LEDST_ON : LEDST_OFF;
3351b3009deSJoseph Chen 		ret = led_set_state(priv->led_full, ledst);
3361b3009deSJoseph Chen 		if (ret) {
3371b3009deSJoseph Chen 			printf("set charging full led %s failed, ret=%d\n",
3381b3009deSJoseph Chen 			       ledst == LEDST_ON ? "ON" : "OFF", ret);
3391b3009deSJoseph Chen 			return ret;
3401b3009deSJoseph Chen 		}
3411b3009deSJoseph Chen 	}
3421b3009deSJoseph Chen 
3431b3009deSJoseph Chen 	return 0;
3441b3009deSJoseph Chen }
3451b3009deSJoseph Chen #else
3461b3009deSJoseph Chen static int leds_update(struct udevice *dev, int soc) { return 0; }
3471b3009deSJoseph Chen #endif
3481b3009deSJoseph Chen 
3494d083e3fSJoseph Chen static int fg_charger_get_chrg_online(struct udevice *dev)
3504d083e3fSJoseph Chen {
3514d083e3fSJoseph Chen 	struct charge_animation_priv *priv = dev_get_priv(dev);
3524d083e3fSJoseph Chen 	struct udevice *charger;
3534d083e3fSJoseph Chen 
3544d083e3fSJoseph Chen 	charger = priv->charger ? : priv->fg;
3554d083e3fSJoseph Chen 
3564d083e3fSJoseph Chen 	return fuel_gauge_get_chrg_online(charger);
3574d083e3fSJoseph Chen }
3584d083e3fSJoseph Chen 
359f23c35a8SJoseph Chen static int charge_extrem_low_power(struct udevice *dev)
360f23c35a8SJoseph Chen {
361f23c35a8SJoseph Chen 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
362f23c35a8SJoseph Chen 	struct charge_animation_priv *priv = dev_get_priv(dev);
363f23c35a8SJoseph Chen 	struct udevice *pmic = priv->pmic;
364f23c35a8SJoseph Chen 	struct udevice *fg = priv->fg;
365f23c35a8SJoseph Chen 	int voltage, soc, charging = 1;
36679244e4cSJoseph Chen 	static int timer_initialized;
3671b3009deSJoseph Chen 	int ret;
368f23c35a8SJoseph Chen 
369f23c35a8SJoseph Chen 	voltage = fuel_gauge_get_voltage(fg);
370f23c35a8SJoseph Chen 	if (voltage < 0)
371f23c35a8SJoseph Chen 		return -EINVAL;
372f23c35a8SJoseph Chen 
373f23c35a8SJoseph Chen 	while (voltage < pdata->low_power_voltage + 50) {
374f23c35a8SJoseph Chen 		/* Check charger online */
3754d083e3fSJoseph Chen 		charging = fg_charger_get_chrg_online(dev);
376f23c35a8SJoseph Chen 		if (charging <= 0) {
37779244e4cSJoseph Chen 			printf("%s: Not charging, online=%d. Shutdown...\n",
37879244e4cSJoseph Chen 			       __func__, charging);
379f23c35a8SJoseph Chen 			/* wait uart flush before shutdown */
38079244e4cSJoseph Chen 			mdelay(5);
381f23c35a8SJoseph Chen 			/* PMIC shutdown */
382f23c35a8SJoseph Chen 			pmic_shutdown(pmic);
383f23c35a8SJoseph Chen 
384f23c35a8SJoseph Chen 			printf("Cpu should never reach here, shutdown failed !\n");
385f23c35a8SJoseph Chen 			continue;
386f23c35a8SJoseph Chen 		}
387f23c35a8SJoseph Chen 
38879244e4cSJoseph Chen 		/* Enable auto wakeup */
38979244e4cSJoseph Chen 		if (!timer_initialized) {
39079244e4cSJoseph Chen 			timer_initialized = 1;
39179244e4cSJoseph Chen 			autowakeup_timer_init(dev, 5);
39279244e4cSJoseph Chen 		}
39379244e4cSJoseph Chen 
394f23c35a8SJoseph Chen 		/*
395f23c35a8SJoseph Chen 		 * Just for fuel gauge to update something important,
396f23c35a8SJoseph Chen 		 * including charge current, coulometer or other.
397f23c35a8SJoseph Chen 		 */
3984f9cab27SJoseph Chen 		soc = fuel_gauge_update_get_soc(fg);
399f23c35a8SJoseph Chen 		if (soc < 0 || soc > 100) {
400f23c35a8SJoseph Chen 			printf("get soc failed: %d\n", soc);
401f23c35a8SJoseph Chen 			continue;
402f23c35a8SJoseph Chen 		}
403f23c35a8SJoseph Chen 
4041b3009deSJoseph Chen 		/* Update led */
4051b3009deSJoseph Chen 		ret = leds_update(dev, soc);
4061b3009deSJoseph Chen 		if (ret)
4071b3009deSJoseph Chen 			printf("update led failed: %d\n", ret);
4081b3009deSJoseph Chen 
409f23c35a8SJoseph Chen 		printf("Extrem low power, force charging... threshold=%dmv, now=%dmv\n",
410f23c35a8SJoseph Chen 		       pdata->low_power_voltage, voltage);
411f23c35a8SJoseph Chen 
412f23c35a8SJoseph Chen 		/* System suspend */
4132e68f6b5SJoseph Chen 		system_suspend_enter(dev);
414f23c35a8SJoseph Chen 
415f23c35a8SJoseph Chen 		/* Update voltage */
416f23c35a8SJoseph Chen 		voltage = fuel_gauge_get_voltage(fg);
417f23c35a8SJoseph Chen 		if (voltage < 0) {
418f23c35a8SJoseph Chen 			printf("get voltage failed: %d\n", voltage);
419f23c35a8SJoseph Chen 			continue;
420f23c35a8SJoseph Chen 		}
4215fc2a70cSJoseph Chen 
4225fc2a70cSJoseph Chen 		if (ctrlc()) {
4235fc2a70cSJoseph Chen 			printf("Extrem low charge: exit by ctrl+c\n");
4245fc2a70cSJoseph Chen 			break;
4255fc2a70cSJoseph Chen 		}
426f23c35a8SJoseph Chen 	}
427f23c35a8SJoseph Chen 
42879244e4cSJoseph Chen 	autowakeup_timer_uninit();
42979244e4cSJoseph Chen 
430f23c35a8SJoseph Chen 	return 0;
431f23c35a8SJoseph Chen }
432f23c35a8SJoseph Chen 
433f1c8ecceSJoseph Chen static int charge_animation_show(struct udevice *dev)
434f1c8ecceSJoseph Chen {
435f1c8ecceSJoseph Chen 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
436f1c8ecceSJoseph Chen 	struct charge_animation_priv *priv = dev_get_priv(dev);
437f1c8ecceSJoseph Chen 	const struct charge_image *image = priv->image;
438f1c8ecceSJoseph Chen 	struct udevice *pmic = priv->pmic;
439f1c8ecceSJoseph Chen 	struct udevice *fg = priv->fg;
440a8b9d026SJoseph Chen 	const char *preboot = env_get("preboot");
441f1c8ecceSJoseph Chen 	int image_num = priv->image_num;
442f1c8ecceSJoseph Chen 	bool ever_lowpower_screen_off = false;
443f1c8ecceSJoseph Chen 	bool screen_on = true;
444f1c8ecceSJoseph Chen 	ulong show_start = 0, charge_start = 0, debug_start = 0;
4455e804741SJoseph Chen 	ulong delta;
446f1c8ecceSJoseph Chen 	ulong ms = 0, sec = 0;
447ebe3d004SJoseph Chen 	int start_idx = 0, show_idx = -1, old_show_idx = IMAGE_RESET_IDX;
448d6653c12SJoseph Chen 	int soc, voltage, current, key_state;
449f23c35a8SJoseph Chen 	int i, charging = 1, ret;
4508f9ff705SJoseph Chen 	int boot_mode;
4515e804741SJoseph Chen 	int first_poll_fg = 1;
452038c1ecaSShunqing Chen 	bool exit_charge = false;
453f1c8ecceSJoseph Chen 
454f23c35a8SJoseph Chen /*
455f23c35a8SJoseph Chen  * Check sequence:
456f23c35a8SJoseph Chen  *
457f23c35a8SJoseph Chen  * 1. Extrem low power charge?
458f23c35a8SJoseph Chen  * 2. Preboot cmd?
459f23c35a8SJoseph Chen  * 3. Valid boot mode?
460f23c35a8SJoseph Chen  * 4. U-Boot charge enabled by dts config?
461f23c35a8SJoseph Chen  * 5. Screen off before charge?
462f23c35a8SJoseph Chen  * 6. Enter charge !
463f23c35a8SJoseph Chen  *
464f23c35a8SJoseph Chen  */
46561a7a6d6SJoseph Chen 	if (!fuel_gauge_bat_is_exist(fg)) {
46661a7a6d6SJoseph Chen 		printf("Exit charge: battery is not exist\n");
46761a7a6d6SJoseph Chen 		return 0;
46861a7a6d6SJoseph Chen 	}
46961a7a6d6SJoseph Chen 
470f23c35a8SJoseph Chen 	/* Extrem low power charge */
471f23c35a8SJoseph Chen 	ret = charge_extrem_low_power(dev);
472f23c35a8SJoseph Chen 	if (ret < 0) {
473f23c35a8SJoseph Chen 		printf("extrem low power charge failed, ret=%d\n", ret);
474f23c35a8SJoseph Chen 		return ret;
475f23c35a8SJoseph Chen 	}
476f23c35a8SJoseph Chen 
477a8b9d026SJoseph Chen 	/* If there is preboot command, exit */
47861a7a6d6SJoseph Chen 	if (preboot && !strstr(preboot, "dvfs")) {
4797ae45834SJoseph Chen 		printf("Exit charge: due to preboot cmd '%s'\n", preboot);
480a8b9d026SJoseph Chen 		return 0;
481a8b9d026SJoseph Chen 	}
482a8b9d026SJoseph Chen 
483f23c35a8SJoseph Chen 	/* Not valid charge mode, exit */
484d3ff9cf9SKever Yang #ifdef CONFIG_RKIMG_BOOTLOADER
4858f9ff705SJoseph Chen 	boot_mode = rockchip_get_boot_mode();
486221b5220SJoseph Chen 	if ((boot_mode != BOOT_MODE_CHARGING) &&
487221b5220SJoseph Chen 	    (boot_mode != BOOT_MODE_UNDEFINE)) {
4887ae45834SJoseph Chen 		printf("Exit charge: due to boot mode\n");
4898f9ff705SJoseph Chen 		return 0;
4908f9ff705SJoseph Chen 	}
4918f9ff705SJoseph Chen #endif
492038c1ecaSShunqing Chen 	charging = fg_charger_get_chrg_online(dev);
493038c1ecaSShunqing Chen 	/* Not charger online and low power, shutdown */
494038c1ecaSShunqing Chen 	if (charging <= 0 && pdata->auto_exit_charge) {
495038c1ecaSShunqing Chen 		soc = fuel_gauge_update_get_soc(fg);
496038c1ecaSShunqing Chen 		voltage = fuel_gauge_get_voltage(fg);
497038c1ecaSShunqing Chen 		if (soc < pdata->exit_charge_level) {
498038c1ecaSShunqing Chen 			printf("soc(%d%%) < exit_charge_level(%d%%)\n",
499038c1ecaSShunqing Chen 			       soc, pdata->exit_charge_level);
500038c1ecaSShunqing Chen 			exit_charge = true;
501038c1ecaSShunqing Chen 		}
502038c1ecaSShunqing Chen 		if (voltage < pdata->exit_charge_voltage) {
503038c1ecaSShunqing Chen 			printf("voltage(%d) < exit_charge_voltage(%d)\n",
504038c1ecaSShunqing Chen 			       voltage, pdata->exit_charge_voltage);
505038c1ecaSShunqing Chen 			exit_charge = true;
506038c1ecaSShunqing Chen 		}
507038c1ecaSShunqing Chen 		if (exit_charge) {
508038c1ecaSShunqing Chen 			printf("Not charging and low power, Shutdown...\n");
509038c1ecaSShunqing Chen 			show_idx = IMAGE_LOWPOWER_IDX(image_num);
510038c1ecaSShunqing Chen 			charge_show_bmp(image[show_idx].name);
511038c1ecaSShunqing Chen 			mdelay(1000);
512038c1ecaSShunqing Chen 			pmic_shutdown(pmic);
513038c1ecaSShunqing Chen 		}
514038c1ecaSShunqing Chen 	}
515221b5220SJoseph Chen 
516616056c9Sshengfei Xu 	/* Not charger online, exit */
517cf49f6adSJoseph Chen 	if (charging <= 0) {
5187ae45834SJoseph Chen 		printf("Exit charge: due to charger offline\n");
519616056c9Sshengfei Xu 		return 0;
520cf49f6adSJoseph Chen 	}
521616056c9Sshengfei Xu 
522f23c35a8SJoseph Chen 	/* Enter android charge, set property for kernel */
523d6653c12SJoseph Chen 	if (pdata->android_charge) {
524d6653c12SJoseph Chen 		env_update("bootargs", "androidboot.mode=charger");
525d6653c12SJoseph Chen 		printf("Android charge mode\n");
526d6653c12SJoseph Chen 	}
527d6653c12SJoseph Chen 
528f23c35a8SJoseph Chen 	/* Not enable U-Boot charge, exit */
529cf49f6adSJoseph Chen 	if (!pdata->uboot_charge) {
5307ae45834SJoseph Chen 		printf("Exit charge: due to not enable uboot charge\n");
531d6653c12SJoseph Chen 		return 0;
532cf49f6adSJoseph Chen 	}
533d6653c12SJoseph Chen 
534f1c8ecceSJoseph Chen 	voltage = fuel_gauge_get_voltage(fg);
535f1c8ecceSJoseph Chen 	if (voltage < 0) {
536f1c8ecceSJoseph Chen 		printf("get voltage failed: %d\n", voltage);
537f1c8ecceSJoseph Chen 		return -EINVAL;
538f1c8ecceSJoseph Chen 	}
539f1c8ecceSJoseph Chen 
540f1c8ecceSJoseph Chen 	/* If low power, turn off screen */
541d6653c12SJoseph Chen 	if (voltage <= pdata->screen_on_voltage + 50) {
542f1c8ecceSJoseph Chen 		screen_on = false;
543f1c8ecceSJoseph Chen 		ever_lowpower_screen_off = true;
544f23c35a8SJoseph Chen 		charge_show_bmp(NULL);
545f1c8ecceSJoseph Chen 	}
546f1c8ecceSJoseph Chen 
547e7f9facbSJoseph Chen 	/* Auto wakeup */
548e7f9facbSJoseph Chen 	if (pdata->auto_wakeup_interval) {
549e7f9facbSJoseph Chen 		printf("Auto wakeup: %dS\n", pdata->auto_wakeup_interval);
55079244e4cSJoseph Chen 		autowakeup_timer_init(dev, pdata->auto_wakeup_interval);
551e7f9facbSJoseph Chen 	}
552e7f9facbSJoseph Chen 
553175257e4SJoseph Chen /* Give a message warning when CONFIG_IRQ is not enabled */
554175257e4SJoseph Chen #ifdef CONFIG_IRQ
555d6653c12SJoseph Chen 	printf("Enter U-Boot charging mode\n");
556175257e4SJoseph Chen #else
557175257e4SJoseph Chen 	printf("Enter U-Boot charging mode(without IRQ)\n");
558175257e4SJoseph Chen #endif
559f1c8ecceSJoseph Chen 
560d6653c12SJoseph Chen 	charge_start = get_timer(0);
5615e804741SJoseph Chen 	delta = get_timer(0);
562e7f9facbSJoseph Chen 
563f1c8ecceSJoseph Chen 	/* Charging ! */
564f1c8ecceSJoseph Chen 	while (1) {
5655e804741SJoseph Chen 		/*
5665e804741SJoseph Chen 		 * At the most time, fuel gauge is usually a i2c device, we
5675e804741SJoseph Chen 		 * should avoid read/write all the time. We had better set
5685e804741SJoseph Chen 		 * poll seconds to update fuel gauge info.
5695e804741SJoseph Chen 		 */
5705e804741SJoseph Chen 		if (!first_poll_fg && get_timer(delta) < FUEL_GAUGE_POLL_MS)
5715e804741SJoseph Chen 			goto show_images;
5725e804741SJoseph Chen 
5735e804741SJoseph Chen 		delta = get_timer(0);
5745e804741SJoseph Chen 
575f1c8ecceSJoseph Chen 		debug("step1 (%d)... \n", screen_on);
576f1c8ecceSJoseph Chen 
577fd62311eSJoseph Chen 		/*
578fd62311eSJoseph Chen 		 * Most fuel gauge is I2C interface, it shouldn't be interrupted
579ebe3d004SJoseph Chen 		 * during transfer. The power key event depends on interrupt, so
580ebe3d004SJoseph Chen 		 * we should disable local irq when update fuel gauge.
581fd62311eSJoseph Chen 		 */
582fd62311eSJoseph Chen 		local_irq_disable();
583fd62311eSJoseph Chen 
584f1c8ecceSJoseph Chen 		/* Step1: Is charging now ? */
5854d083e3fSJoseph Chen 		charging = fg_charger_get_chrg_online(dev);
586f1c8ecceSJoseph Chen 		if (charging <= 0) {
587f1c8ecceSJoseph Chen 			printf("Not charging, online=%d. Shutdown...\n",
588f1c8ecceSJoseph Chen 			       charging);
589255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
590255e5751SWenping Zhang 			/*
591*77bac292SZorro Liu 			 * If charger is plug out during charging, display poweroff
592*77bac292SZorro Liu 			 * image before device power off.
593255e5751SWenping Zhang 			 * Irq must be enable if CONFIG_IRQ is defined, because
594255e5751SWenping Zhang 			 * ebc need to wait irq to indicate frame is complete.
595255e5751SWenping Zhang 			 */
596255e5751SWenping Zhang #ifdef CONFIG_IRQ
597255e5751SWenping Zhang 			local_irq_enable();
598255e5751SWenping Zhang #endif
599*77bac292SZorro Liu 			ret = rockchip_eink_show_charge_logo(EINK_LOGO_POWEROFF);
600255e5751SWenping Zhang 			if (ret != 0)
601255e5751SWenping Zhang 				printf("Eink display reset logo failed\n");
602255e5751SWenping Zhang #ifdef CONFIG_IRQ
603255e5751SWenping Zhang 			local_irq_disable();
604255e5751SWenping Zhang #endif
605255e5751SWenping Zhang #endif
606f1c8ecceSJoseph Chen 			/* wait uart flush before shutdown */
60779244e4cSJoseph Chen 			mdelay(5);
608f1c8ecceSJoseph Chen 
609f1c8ecceSJoseph Chen 			/* PMIC shutdown */
610f1c8ecceSJoseph Chen 			pmic_shutdown(pmic);
611f1c8ecceSJoseph Chen 
612f1c8ecceSJoseph Chen 			printf("Cpu should never reach here, shutdown failed !\n");
613f1c8ecceSJoseph Chen 			continue;
614f1c8ecceSJoseph Chen 		}
615f1c8ecceSJoseph Chen 
616f1c8ecceSJoseph Chen 		debug("step2 (%d)... show_idx=%d\n", screen_on, show_idx);
617f1c8ecceSJoseph Chen 
618f1c8ecceSJoseph Chen 		/* Step2: get soc and voltage */
6194f9cab27SJoseph Chen 		soc = fuel_gauge_update_get_soc(fg);
620f1c8ecceSJoseph Chen 		if (soc < 0 || soc > 100) {
621f1c8ecceSJoseph Chen 			printf("get soc failed: %d\n", soc);
622f1c8ecceSJoseph Chen 			continue;
623f1c8ecceSJoseph Chen 		}
624f1c8ecceSJoseph Chen 
625f1c8ecceSJoseph Chen 		voltage = fuel_gauge_get_voltage(fg);
626f1c8ecceSJoseph Chen 		if (voltage < 0) {
627f1c8ecceSJoseph Chen 			printf("get voltage failed: %d\n", voltage);
628f1c8ecceSJoseph Chen 			continue;
629f1c8ecceSJoseph Chen 		}
630f1c8ecceSJoseph Chen 
631d6653c12SJoseph Chen 		current = fuel_gauge_get_current(fg);
632d6653c12SJoseph Chen 		if (current == -ENOSYS) {
633d6653c12SJoseph Chen 			printf("get current failed: %d\n", current);
634d6653c12SJoseph Chen 			continue;
635d6653c12SJoseph Chen 		}
636fd62311eSJoseph Chen 
637ebe3d004SJoseph Chen 		first_poll_fg = 0;
638fd62311eSJoseph Chen 		local_irq_enable();
639fd62311eSJoseph Chen 
640038c1ecaSShunqing Chen 		if (pdata->auto_exit_charge) {
641038c1ecaSShunqing Chen 			/* Is able to boot now ? */
642038c1ecaSShunqing Chen 			if (pdata->exit_charge_level &&
643038c1ecaSShunqing Chen 			    soc >= pdata->exit_charge_level) {
644038c1ecaSShunqing Chen 				printf("soc(%d%%) exit charge animation...\n",
645038c1ecaSShunqing Chen 				       soc);
646038c1ecaSShunqing Chen 				break;
647038c1ecaSShunqing Chen 			}
648038c1ecaSShunqing Chen 			if (pdata->exit_charge_voltage &&
649038c1ecaSShunqing Chen 			    voltage >= pdata->exit_charge_voltage) {
650038c1ecaSShunqing Chen 				printf("vol(%d) exit charge animation...\n",
651038c1ecaSShunqing Chen 				       voltage);
652038c1ecaSShunqing Chen 				break;
653038c1ecaSShunqing Chen 			}
654038c1ecaSShunqing Chen 		}
655038c1ecaSShunqing Chen 
6565e804741SJoseph Chen show_images:
657f1c8ecceSJoseph Chen 		/*
658f1c8ecceSJoseph Chen 		 * Just for debug, otherwise there will be nothing output which
659f1c8ecceSJoseph Chen 		 * is not good to know what happen.
660f1c8ecceSJoseph Chen 		 */
661f1c8ecceSJoseph Chen 		if (!debug_start)
662f1c8ecceSJoseph Chen 			debug_start = get_timer(0);
663f1c8ecceSJoseph Chen 		if (get_timer(debug_start) > 20000) {
664f1c8ecceSJoseph Chen 			debug_start = get_timer(0);
665ebe3d004SJoseph Chen 			printf("[%8ld]: soc=%d%%, vol=%dmv, c=%dma, "
666ebe3d004SJoseph Chen 			       "online=%d, screen_on=%d\n",
667d6653c12SJoseph Chen 			       get_timer(0)/1000, soc, voltage,
668d6653c12SJoseph Chen 			       current, charging, screen_on);
669f1c8ecceSJoseph Chen 		}
670f1c8ecceSJoseph Chen 
6711b3009deSJoseph Chen 		/* Update leds */
6721b3009deSJoseph Chen 		ret = leds_update(dev, soc);
6731b3009deSJoseph Chen 		if (ret)
6741b3009deSJoseph Chen 			printf("update led failed: %d\n", ret);
6751b3009deSJoseph Chen 
676f1c8ecceSJoseph Chen 		/*
677f23c35a8SJoseph Chen 		 * If ever lowpower screen off, force screen_on=false, which
678f1c8ecceSJoseph Chen 		 * means key event can't modify screen_on, only voltage higher
679f1c8ecceSJoseph Chen 		 * then threshold can update screen_on=true;
680f1c8ecceSJoseph Chen 		 */
681f1c8ecceSJoseph Chen 		if (ever_lowpower_screen_off)
682f1c8ecceSJoseph Chen 			screen_on = false;
683f1c8ecceSJoseph Chen 
684f1c8ecceSJoseph Chen 		/*
685f1c8ecceSJoseph Chen 		 * Auto turn on screen when voltage higher than Vol screen on.
686ebe3d004SJoseph Chen 		 * 'ever_lowpower_screen_off' means enter the while(1) loop with
687f1c8ecceSJoseph Chen 		 * screen off.
688f1c8ecceSJoseph Chen 		 */
689f1c8ecceSJoseph Chen 		if ((ever_lowpower_screen_off) &&
690d6653c12SJoseph Chen 		    (voltage > pdata->screen_on_voltage)) {
691f1c8ecceSJoseph Chen 			ever_lowpower_screen_off = false;
692f1c8ecceSJoseph Chen 			screen_on = true;
693ebe3d004SJoseph Chen 			show_idx = IMAGE_RESET_IDX;
694f1c8ecceSJoseph Chen 		}
695f1c8ecceSJoseph Chen 
696f1c8ecceSJoseph Chen 		/*
697ebe3d004SJoseph Chen 		 * IMAGE_RESET_IDX means show_idx show be update by start_idx.
698f1c8ecceSJoseph Chen 		 * When short key pressed event trigged, we will set show_idx
699ebe3d004SJoseph Chen 		 * as IMAGE_RESET_IDX which updates images index from start_idx
700f1c8ecceSJoseph Chen 		 * that calculate by current soc.
701f1c8ecceSJoseph Chen 		 */
702ebe3d004SJoseph Chen 		if (show_idx == IMAGE_RESET_IDX) {
703ebe3d004SJoseph Chen 			for (i = 0; i < IMAGE_SOC_100_IDX(image_num); i++) {
704f1c8ecceSJoseph Chen 				/* Find out which image we start to show */
705f1c8ecceSJoseph Chen 				if ((soc >= image[i].soc) &&
706f1c8ecceSJoseph Chen 				    (soc < image[i + 1].soc)) {
707f1c8ecceSJoseph Chen 					start_idx = i;
708f1c8ecceSJoseph Chen 					break;
709f1c8ecceSJoseph Chen 				}
710f1c8ecceSJoseph Chen 
711f1c8ecceSJoseph Chen 				if (soc >= 100) {
712ebe3d004SJoseph Chen 					start_idx = IMAGE_SOC_100_IDX(image_num);
713f1c8ecceSJoseph Chen 					break;
714f1c8ecceSJoseph Chen 				}
715f1c8ecceSJoseph Chen 			}
716f1c8ecceSJoseph Chen 
717f1c8ecceSJoseph Chen 			debug("%s: show_idx=%d, screen_on=%d\n",
718f1c8ecceSJoseph Chen 			      __func__, show_idx, screen_on);
719f1c8ecceSJoseph Chen 
720f1c8ecceSJoseph Chen 			/* Mark start index and start time */
721f1c8ecceSJoseph Chen 			show_idx = start_idx;
722f1c8ecceSJoseph Chen 			show_start = get_timer(0);
723f1c8ecceSJoseph Chen 		}
724f1c8ecceSJoseph Chen 
725f1c8ecceSJoseph Chen 		debug("step3 (%d)... show_idx=%d\n", screen_on, show_idx);
726f1c8ecceSJoseph Chen 
727255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
728255e5751SWenping Zhang 		/*
729255e5751SWenping Zhang 		 * Device is auto wakeup from suspend, if it's eink display,
730255e5751SWenping Zhang 		 * screen will display the last image after suspend, so
731255e5751SWenping Zhang 		 * we should update the image to show the approximate
732255e5751SWenping Zhang 		 * battery power if battery is charging to next level.
733255e5751SWenping Zhang 		 */
734255e5751SWenping Zhang 		if (pdata->auto_wakeup_interval &&
735255e5751SWenping Zhang 		    priv->auto_wakeup_key_state == KEY_PRESS_DOWN &&
736255e5751SWenping Zhang 		    !screen_on) {
737255e5751SWenping Zhang 			if (soc >= image[old_show_idx + 1].soc &&
738255e5751SWenping Zhang 			    soc < 100) {
739255e5751SWenping Zhang 				int ret;
740255e5751SWenping Zhang 				int logo_type = EINK_LOGO_CHARGING_0;
741255e5751SWenping Zhang 
742255e5751SWenping Zhang 				logo_type = logo_type << (old_show_idx + 1);
743255e5751SWenping Zhang 				ret = rockchip_eink_show_charge_logo(logo_type);
744255e5751SWenping Zhang 				/*
745255e5751SWenping Zhang 				 * only change the logic if eink is
746255e5751SWenping Zhang 				 * actually exist
747255e5751SWenping Zhang 				 */
748255e5751SWenping Zhang 				if (ret == 0) {
749255e5751SWenping Zhang 					printf("Update image id[%d] for eink\n",
750255e5751SWenping Zhang 					       old_show_idx + 1);
751255e5751SWenping Zhang 					old_show_idx++;
752255e5751SWenping Zhang 				}
753255e5751SWenping Zhang 			}
754255e5751SWenping Zhang 		}
755255e5751SWenping Zhang 		/*
756255e5751SWenping Zhang 		 * If battery capacity is charged to 100%, exit charging
757255e5751SWenping Zhang 		 * animation and boot android system.
758255e5751SWenping Zhang 		 */
759255e5751SWenping Zhang 		if (soc >= 100) {
760255e5751SWenping Zhang 			int ret;
761255e5751SWenping Zhang 			int logo_type = EINK_LOGO_CHARGING_5;
762255e5751SWenping Zhang 
763255e5751SWenping Zhang 			ret = rockchip_eink_show_charge_logo(logo_type);
764255e5751SWenping Zhang 			/* Only change the logic if eink is acutally exist */
765255e5751SWenping Zhang 			if (ret == 0) {
766255e5751SWenping Zhang 				printf("battery FULL,exit charge animation\n");
767255e5751SWenping Zhang 				mdelay(20);
768255e5751SWenping Zhang 				break;
769255e5751SWenping Zhang 			}
770255e5751SWenping Zhang 		}
771255e5751SWenping Zhang #endif
772f1c8ecceSJoseph Chen 		/* Step3: show images */
773f1c8ecceSJoseph Chen 		if (screen_on) {
77493aee2d9SJoseph Chen 			/* Don't call 'charge_show_bmp' unless image changed */
77593aee2d9SJoseph Chen 			if (old_show_idx != show_idx) {
776255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
777255e5751SWenping Zhang 				int logo_type = EINK_LOGO_CHARGING_0;
778255e5751SWenping Zhang 
779255e5751SWenping Zhang 				rockchip_eink_show_charge_logo(logo_type <<
780255e5751SWenping Zhang 							       show_idx);
781255e5751SWenping Zhang #endif
78293aee2d9SJoseph Chen 				old_show_idx = show_idx;
783f1c8ecceSJoseph Chen 				debug("SHOW: %s\n", image[show_idx].name);
784f23c35a8SJoseph Chen 				charge_show_bmp(image[show_idx].name);
78593aee2d9SJoseph Chen 			}
786ebe3d004SJoseph Chen 			/* Re-calculate timeout to off screen */
787e7f9facbSJoseph Chen 			if (priv->auto_screen_off_timeout == 0)
788e7f9facbSJoseph Chen 				priv->auto_screen_off_timeout = get_timer(0);
789f1c8ecceSJoseph Chen 		} else {
790e7f9facbSJoseph Chen 			priv->auto_screen_off_timeout = 0;
7912e68f6b5SJoseph Chen 			system_suspend_enter(dev);
792f1c8ecceSJoseph Chen 		}
793f1c8ecceSJoseph Chen 
794f1c8ecceSJoseph Chen 		mdelay(5);
795f1c8ecceSJoseph Chen 
796f1c8ecceSJoseph Chen 		/* Every image shows period */
797f1c8ecceSJoseph Chen 		if (get_timer(show_start) > image[show_idx].period) {
798f1c8ecceSJoseph Chen 			show_start = get_timer(0);
799f1c8ecceSJoseph Chen 			/* Update to next image */
800f1c8ecceSJoseph Chen 			show_idx++;
801ebe3d004SJoseph Chen 			if (show_idx > IMAGE_SOC_100_IDX(image_num))
802ebe3d004SJoseph Chen 				show_idx = IMAGE_RESET_IDX;
803f1c8ecceSJoseph Chen 		}
804f1c8ecceSJoseph Chen 
805f1c8ecceSJoseph Chen 		debug("step4 (%d)... \n", screen_on);
806f1c8ecceSJoseph Chen 
807f1c8ecceSJoseph Chen 		/*
808f1c8ecceSJoseph Chen 		 * Step4: check key event.
809f1c8ecceSJoseph Chen 		 *
810f1c8ecceSJoseph Chen 		 * Short key event: turn on/off screen;
811f1c8ecceSJoseph Chen 		 * Long key event: show logo and boot system or still charging.
812f1c8ecceSJoseph Chen 		 */
813e7f9facbSJoseph Chen 		key_state = check_key_press(dev);
814b177a917SJoseph Chen 		if (key_state == KEY_PRESS_DOWN) {
815f1c8ecceSJoseph Chen 			/*
816f1c8ecceSJoseph Chen 			 * Clear current image index, and show image
817f1c8ecceSJoseph Chen 			 * from start_idx
818f1c8ecceSJoseph Chen 			 */
819ebe3d004SJoseph Chen 			old_show_idx = IMAGE_RESET_IDX;
820ebe3d004SJoseph Chen 			show_idx = IMAGE_RESET_IDX;
821f1c8ecceSJoseph Chen 
822f1c8ecceSJoseph Chen 			/*
823ebe3d004SJoseph Chen 			 *	Reverse the screen state
824f1c8ecceSJoseph Chen 			 *
825f1c8ecceSJoseph Chen 			 * If screen_on=false, means this short key pressed
826f1c8ecceSJoseph Chen 			 * event turn on the screen and we need show images.
827f1c8ecceSJoseph Chen 			 *
828f1c8ecceSJoseph Chen 			 * If screen_on=true, means this short key pressed
829f1c8ecceSJoseph Chen 			 * event turn off the screen and we never show images.
830f1c8ecceSJoseph Chen 			 */
831ebe3d004SJoseph Chen 			if (screen_on) {
832255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY
833255e5751SWenping Zhang 				int type = EINK_LOGO_CHARGING_0 << start_idx;
834255e5751SWenping Zhang 				/*
835255e5751SWenping Zhang 				 * Show current battery capacity before suspend
836255e5751SWenping Zhang 				 * if it's eink display, because eink screen
837255e5751SWenping Zhang 				 * will continue to display the last image
838255e5751SWenping Zhang 				 * after suspend, so user can get the
839255e5751SWenping Zhang 				 * approximate capacity by image displayed.
840255e5751SWenping Zhang 				 */
841255e5751SWenping Zhang 				ret = rockchip_eink_show_charge_logo(type);
842255e5751SWenping Zhang 				/* only change the logic if eink display ok */
843255e5751SWenping Zhang 				if (ret == 0)
844255e5751SWenping Zhang 					old_show_idx = start_idx;
845255e5751SWenping Zhang #endif
846ebe3d004SJoseph Chen 				charge_show_bmp(NULL); /* Turn off screen */
847f1c8ecceSJoseph Chen 				screen_on = false;
8482e68f6b5SJoseph Chen 				priv->suspend_delay_timeout = get_timer(0);
849ebe3d004SJoseph Chen 			} else {
850f1c8ecceSJoseph Chen 				screen_on = true;
851ebe3d004SJoseph Chen 			}
8522e68f6b5SJoseph Chen 
8532e68f6b5SJoseph Chen 			printf("screen %s\n", screen_on ? "on" : "off");
854b177a917SJoseph Chen 		} else if (key_state == KEY_PRESS_LONG_DOWN) {
855ebe3d004SJoseph Chen 			/* Set screen_on=true anyway when key long pressed */
856f1c8ecceSJoseph Chen 			if (!screen_on)
857f1c8ecceSJoseph Chen 				screen_on = true;
858f1c8ecceSJoseph Chen 
8592e68f6b5SJoseph Chen 			printf("screen %s\n", screen_on ? "on" : "off");
8602e68f6b5SJoseph Chen 
861f1c8ecceSJoseph Chen 			/* Is able to boot now ? */
862d6653c12SJoseph Chen 			if (soc < pdata->exit_charge_level) {
863f1c8ecceSJoseph Chen 				printf("soc=%d%%, threshold soc=%d%%\n",
864d6653c12SJoseph Chen 				       soc, pdata->exit_charge_level);
865f1c8ecceSJoseph Chen 				printf("Low power, unable to boot, charging...\n");
866ebe3d004SJoseph Chen 				show_idx = IMAGE_LOWPOWER_IDX(image_num);
867f1c8ecceSJoseph Chen 				continue;
868f1c8ecceSJoseph Chen 			}
869f1c8ecceSJoseph Chen 
870d6653c12SJoseph Chen 			if (voltage < pdata->exit_charge_voltage) {
871f1c8ecceSJoseph Chen 				printf("voltage=%dmv, threshold voltage=%dmv\n",
872d6653c12SJoseph Chen 				       voltage, pdata->exit_charge_voltage);
873f1c8ecceSJoseph Chen 				printf("Low power, unable to boot, charging...\n");
874ebe3d004SJoseph Chen 				show_idx = IMAGE_LOWPOWER_IDX(image_num);
875f1c8ecceSJoseph Chen 				continue;
876f1c8ecceSJoseph Chen 			}
877f1c8ecceSJoseph Chen 
878f1c8ecceSJoseph Chen 			/* Success exit charging */
879f1c8ecceSJoseph Chen 			printf("Exit charge animation...\n");
880f23c35a8SJoseph Chen 			charge_show_logo();
881f1c8ecceSJoseph Chen 			break;
882f1c8ecceSJoseph Chen 		} else {
883f1c8ecceSJoseph Chen 			/* Do nothing */
884f1c8ecceSJoseph Chen 		}
885f1c8ecceSJoseph Chen 
886f1c8ecceSJoseph Chen 		debug("step5 (%d)... \n", screen_on);
887f1c8ecceSJoseph Chen 
888d6653c12SJoseph Chen 		/* Step5: Exit by ctrl+c */
8891367bfe3SJoseph Chen 		if (ctrlc()) {
890d6653c12SJoseph Chen 			if (voltage >= pdata->screen_on_voltage)
891f23c35a8SJoseph Chen 				charge_show_logo();
8921367bfe3SJoseph Chen 			printf("Exit charge, due to ctrl+c\n");
8931367bfe3SJoseph Chen 			break;
8941367bfe3SJoseph Chen 		}
895f1c8ecceSJoseph Chen 	}
896f1c8ecceSJoseph Chen 
897e7f9facbSJoseph Chen 	if (pdata->auto_wakeup_interval)
898e7f9facbSJoseph Chen 		autowakeup_timer_uninit();
899e7f9facbSJoseph Chen 
900f1c8ecceSJoseph Chen 	ms = get_timer(charge_start);
901f1c8ecceSJoseph Chen 	if (ms >= 1000) {
902f1c8ecceSJoseph Chen 		sec = ms / 1000;
903f1c8ecceSJoseph Chen 		ms = ms % 1000;
904f1c8ecceSJoseph Chen 	}
905f1c8ecceSJoseph Chen 
906f1c8ecceSJoseph Chen 	printf("charging time total: %lu.%lus, soc=%d%%, vol=%dmv\n",
907f1c8ecceSJoseph Chen 	       sec, ms, soc, voltage);
908f1c8ecceSJoseph Chen 
909f1c8ecceSJoseph Chen 	return 0;
910f1c8ecceSJoseph Chen }
911f1c8ecceSJoseph Chen 
9124d083e3fSJoseph Chen static int fg_charger_get_device(struct udevice **fuel_gauge,
9134d083e3fSJoseph Chen 				 struct udevice **charger)
9144d083e3fSJoseph Chen {
9154d083e3fSJoseph Chen 	struct udevice *dev;
9164d083e3fSJoseph Chen 	struct uclass *uc;
9174d083e3fSJoseph Chen 	int ret, cap;
9184d083e3fSJoseph Chen 
9194d083e3fSJoseph Chen 	*fuel_gauge = NULL,
9204d083e3fSJoseph Chen 	*charger = NULL;
9214d083e3fSJoseph Chen 
9224d083e3fSJoseph Chen 	ret = uclass_get(UCLASS_FG, &uc);
9234d083e3fSJoseph Chen 	if (ret)
9244d083e3fSJoseph Chen 		return ret;
9254d083e3fSJoseph Chen 
9264d083e3fSJoseph Chen 	for (uclass_first_device(UCLASS_FG, &dev);
9274d083e3fSJoseph Chen 	     dev;
9284d083e3fSJoseph Chen 	     uclass_next_device(&dev)) {
9294d083e3fSJoseph Chen 		cap = fuel_gauge_capability(dev);
9304d083e3fSJoseph Chen 		if (cap == (FG_CAP_CHARGER | FG_CAP_FUEL_GAUGE)) {
9314d083e3fSJoseph Chen 			*fuel_gauge = dev;
9324d083e3fSJoseph Chen 			*charger = NULL;
9334d083e3fSJoseph Chen 		} else if (cap == FG_CAP_FUEL_GAUGE) {
9344d083e3fSJoseph Chen 			*fuel_gauge = dev;
9354d083e3fSJoseph Chen 		} else if (cap == FG_CAP_CHARGER) {
9364d083e3fSJoseph Chen 			*charger = dev;
9374d083e3fSJoseph Chen 		}
9384d083e3fSJoseph Chen 	}
9394d083e3fSJoseph Chen 
9404d083e3fSJoseph Chen 	return (*fuel_gauge) ? 0 : -ENODEV;
9414d083e3fSJoseph Chen }
9424d083e3fSJoseph Chen 
943f1c8ecceSJoseph Chen static const struct dm_charge_display_ops charge_animation_ops = {
944f1c8ecceSJoseph Chen 	.show = charge_animation_show,
945f1c8ecceSJoseph Chen };
946f1c8ecceSJoseph Chen 
947f1c8ecceSJoseph Chen static int charge_animation_probe(struct udevice *dev)
948f1c8ecceSJoseph Chen {
949f1c8ecceSJoseph Chen 	struct charge_animation_priv *priv = dev_get_priv(dev);
950f23c35a8SJoseph Chen 	int ret, soc;
951f1c8ecceSJoseph Chen 
952f23c35a8SJoseph Chen 	/* Get PMIC: used for power off system  */
953ebe3d004SJoseph Chen 	ret = uclass_get_device(UCLASS_PMIC, 0, &priv->pmic);
954f1c8ecceSJoseph Chen 	if (ret) {
955cf49f6adSJoseph Chen 		if (ret == -ENODEV)
956cf49f6adSJoseph Chen 			printf("Can't find PMIC\n");
957cf49f6adSJoseph Chen 		else
958f1c8ecceSJoseph Chen 			printf("Get UCLASS PMIC failed: %d\n", ret);
959f1c8ecceSJoseph Chen 		return ret;
960f1c8ecceSJoseph Chen 	}
961f1c8ecceSJoseph Chen 
9624d083e3fSJoseph Chen 	/* Get fuel gauge and charger(If need) */
9634d083e3fSJoseph Chen 	ret = fg_charger_get_device(&priv->fg, &priv->charger);
964f1c8ecceSJoseph Chen 	if (ret) {
965cf49f6adSJoseph Chen 		if (ret == -ENODEV)
96631ab5b3bSJoseph Chen 			debug("Can't find FG\n");
967cf49f6adSJoseph Chen 		else
96831ab5b3bSJoseph Chen 			debug("Get UCLASS FG failed: %d\n", ret);
969f1c8ecceSJoseph Chen 		return ret;
970f1c8ecceSJoseph Chen 	}
971f1c8ecceSJoseph Chen 
9728b436ce5SElaine Zhang 	/* Get rtc: used for power on */
973ebe3d004SJoseph Chen 	ret = uclass_get_device(UCLASS_RTC, 0, &priv->rtc);
9748b436ce5SElaine Zhang 	if (ret) {
9758b436ce5SElaine Zhang 		if (ret == -ENODEV)
9768b436ce5SElaine Zhang 			debug("Can't find RTC\n");
9778b436ce5SElaine Zhang 		else
9788b436ce5SElaine Zhang 			debug("Get UCLASS RTC failed: %d\n", ret);
9798b436ce5SElaine Zhang 	}
9808b436ce5SElaine Zhang 
98179244e4cSJoseph Chen 	/* Get PWRKEY: used for wakeup and turn off/on LCD */
98231ab5b3bSJoseph Chen 	if (key_read(KEY_POWER) == KEY_NOT_EXIST) {
98331ab5b3bSJoseph Chen 		debug("Can't find power key\n");
984f23c35a8SJoseph Chen 		return -EINVAL;
985f23c35a8SJoseph Chen 	}
986f23c35a8SJoseph Chen 
987f23c35a8SJoseph Chen 	/* Initialize charge current */
9884f9cab27SJoseph Chen 	soc = fuel_gauge_update_get_soc(priv->fg);
989f23c35a8SJoseph Chen 	if (soc < 0 || soc > 100) {
99031ab5b3bSJoseph Chen 		debug("get soc failed: %d\n", soc);
991f23c35a8SJoseph Chen 		return -EINVAL;
992f23c35a8SJoseph Chen 	}
993f23c35a8SJoseph Chen 
9941b3009deSJoseph Chen 	/* Get leds */
9951b3009deSJoseph Chen #ifdef CONFIG_LED
9961b3009deSJoseph Chen 	ret = led_get_by_label(LED_CHARGING_NAME, &priv->led_charging);
9971b3009deSJoseph Chen 	if (!ret)
9981b3009deSJoseph Chen 		printf("Found Charging LED\n");
9991b3009deSJoseph Chen 	ret = led_get_by_label(LED_CHARGING_FULL_NAME, &priv->led_full);
10001b3009deSJoseph Chen 	if (!ret)
10011b3009deSJoseph Chen 		printf("Found Charging-Full LED\n");
10021b3009deSJoseph Chen #endif
10031b3009deSJoseph Chen 
1004f23c35a8SJoseph Chen 	/* Get charge images */
1005f1c8ecceSJoseph Chen 	priv->image = image;
1006f1c8ecceSJoseph Chen 	priv->image_num = ARRAY_SIZE(image);
1007f1c8ecceSJoseph Chen 
1008f1c8ecceSJoseph Chen 	printf("Enable charge animation display\n");
1009f1c8ecceSJoseph Chen 
1010f1c8ecceSJoseph Chen 	return 0;
1011f1c8ecceSJoseph Chen }
1012f1c8ecceSJoseph Chen 
1013f1c8ecceSJoseph Chen static const struct udevice_id charge_animation_ids[] = {
1014d6653c12SJoseph Chen 	{ .compatible = "rockchip,uboot-charge" },
1015f1c8ecceSJoseph Chen 	{ },
1016f1c8ecceSJoseph Chen };
1017f1c8ecceSJoseph Chen 
1018f1c8ecceSJoseph Chen U_BOOT_DRIVER(charge_animation) = {
1019f1c8ecceSJoseph Chen 	.name = "charge-animation",
1020f1c8ecceSJoseph Chen 	.id = UCLASS_CHARGE_DISPLAY,
1021f1c8ecceSJoseph Chen 	.probe = charge_animation_probe,
1022f1c8ecceSJoseph Chen 	.of_match = charge_animation_ids,
1023f1c8ecceSJoseph Chen 	.ops = &charge_animation_ops,
1024f1c8ecceSJoseph Chen 	.ofdata_to_platdata = charge_animation_ofdata_to_platdata,
1025f1c8ecceSJoseph Chen 	.platdata_auto_alloc_size = sizeof(struct charge_animation_pdata),
1026f1c8ecceSJoseph Chen 	.priv_auto_alloc_size = sizeof(struct charge_animation_priv),
1027f1c8ecceSJoseph Chen };
1028