xref: /rk3399_rockchip-uboot/drivers/power/charge_animation.c (revision c39a6706c52f4ed2c6a025f085b614255d60e8d4)
1 /*
2  * (C) Copyright 2017 Rockchip Electronics Co., Ltd
3  *
4  * SPDX-License-Identifier:     GPL-2.0+
5  */
6 
7 #include <common.h>
8 #include <boot_rkimg.h>
9 #include <console.h>
10 #include <dm.h>
11 #include <errno.h>
12 #include <key.h>
13 #include <pwm.h>
14 #include <irq-generic.h>
15 #include <asm/arch/rockchip_smccc.h>
16 #include <asm/suspend.h>
17 #include <linux/input.h>
18 #include <power/charge_display.h>
19 #include <power/charge_animation.h>
20 #include <power/rockchip_pm.h>
21 #include <power/fuel_gauge.h>
22 #include <power/pmic.h>
23 #include <power/rk8xx_pmic.h>
24 #include <power/regulator.h>
25 #include <video_rockchip.h>
26 
27 DECLARE_GLOBAL_DATA_PTR;
28 
29 #define IMAGE_SHOW_RESET			-1
30 
31 struct charge_image {
32 	const char *name;
33 	int soc;
34 	int period;	/* ms */
35 };
36 
37 struct charge_animation_priv {
38 	struct udevice *pmic;
39 	struct udevice *fg;
40 	const struct charge_image *image;
41 	int image_num;
42 };
43 
44 /*
45  * IF you want to use your own charge images, please:
46  *
47  * 1. Update the following 'image[]' to point to your own images;
48  * 2. You must set the failed image as last one and soc = -1 !!!
49  */
50 static const struct charge_image image[] = {
51 	{ .name = "battery_0.bmp", .soc = 5, .period = 600 },
52 	{ .name = "battery_1.bmp", .soc = 20, .period = 600 },
53 	{ .name = "battery_2.bmp", .soc = 40, .period = 600 },
54 	{ .name = "battery_3.bmp", .soc = 60, .period = 600 },
55 	{ .name = "battery_4.bmp", .soc = 80, .period = 600 },
56 	{ .name = "battery_5.bmp", .soc = 100, .period = 600 },
57 	{ .name = "battery_fail.bmp", .soc = -1, .period = 1000 },
58 };
59 
60 static int charge_animation_ofdata_to_platdata(struct udevice *dev)
61 {
62 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
63 
64 	/* charge mode */
65 	pdata->uboot_charge =
66 		dev_read_u32_default(dev, "rockchip,uboot-charge-on", 0);
67 	pdata->android_charge =
68 		dev_read_u32_default(dev, "rockchip,android-charge-on", 0);
69 
70 	pdata->exit_charge_level =
71 		dev_read_u32_default(dev, "rockchip,uboot-exit-charge-level", 0);
72 	pdata->exit_charge_voltage =
73 		dev_read_u32_default(dev, "rockchip,uboot-exit-charge-voltage", 0);
74 
75 	pdata->low_power_voltage =
76 		dev_read_u32_default(dev, "rockchip,uboot-low-power-voltage", 0);
77 
78 	pdata->screen_on_voltage =
79 		dev_read_u32_default(dev, "rockchip,screen-on-voltage", 0);
80 	pdata->system_suspend =
81 		dev_read_u32_default(dev, "rockchip,system-suspend", 0);
82 
83 	if (pdata->screen_on_voltage > pdata->exit_charge_voltage)
84 		pdata->screen_on_voltage = pdata->exit_charge_voltage;
85 
86 	debug("mode: uboot=%d, android=%d; exit: soc=%d%%, voltage=%dmv;\n"
87 	      "lp_voltage=%d%%, screen_on=%dmv\n",
88 	      pdata->uboot_charge, pdata->android_charge,
89 	      pdata->exit_charge_level, pdata->exit_charge_voltage,
90 	      pdata->low_power_voltage, pdata->screen_on_voltage);
91 
92 	return 0;
93 }
94 
95 static int check_key_press(void)
96 {
97 	u32 state;
98 
99 	state = platform_key_read(KEY_POWER);
100 	if (state < 0)
101 		printf("read power key failed: %d\n", state);
102 
103 	if (state == KEY_PRESS_LONG_DOWN)
104 		printf("power key long pressed...\n");
105 	else if (state == KEY_PRESS_DOWN)
106 		printf("power key short pressed...\n");
107 
108 	return state;
109 }
110 
111 static int system_suspend_enter(struct charge_animation_pdata *pdata)
112 {
113 	/*
114 	 * TODO: enter low power mode:
115 	 * 3. auto turn off screen when timout;
116 	 * 4. power key wakeup;
117 	 * 5. timer period wakeup for pmic fg
118 	 */
119 	if (pdata->system_suspend && IS_ENABLED(CONFIG_ARM_SMCCC)) {
120 		printf("\nSystem suspend: ");
121 		putc('1');
122 		local_irq_disable();
123 		putc('2');
124 		irqs_suspend();
125 		putc('3');
126 		device_suspend();
127 		putc('4');
128 		putc('\n');
129 
130 		/* Trap into ATF for low power mode */
131 		cpu_suspend(0, psci_system_suspend);
132 
133 		putc('\n');
134 		putc('4');
135 		device_resume();
136 		putc('3');
137 		irqs_resume();
138 		putc('2');
139 		local_irq_enable();
140 		putc('1');
141 		putc('\n');
142 
143 		/*
144 		 * We must wait for key release event finish, otherwise
145 		 * we may read key state too early.
146 		 */
147 		mdelay(300);
148 	} else {
149 		printf("\nWfi\n");
150 		wfi();
151 	}
152 
153 	return 0;
154 }
155 
156 #ifdef CONFIG_DRM_ROCKCHIP
157 static void charge_show_bmp(const char *name)
158 {
159 	rockchip_show_bmp(name);
160 }
161 
162 static void charge_show_logo(void)
163 {
164 	rockchip_show_logo();
165 }
166 #else
167 static void charge_show_bmp(const char *name) {}
168 static void charge_show_logo(void) {}
169 #endif
170 
171 static int charge_extrem_low_power(struct udevice *dev)
172 {
173 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
174 	struct charge_animation_priv *priv = dev_get_priv(dev);
175 	struct udevice *pmic = priv->pmic;
176 	struct udevice *fg = priv->fg;
177 	int voltage, soc, charging = 1;
178 
179 	voltage = fuel_gauge_get_voltage(fg);
180 	if (voltage < 0)
181 		return -EINVAL;
182 
183 	while (voltage < pdata->low_power_voltage + 50) {
184 		/* Check charger online */
185 		charging = fuel_gauge_get_chrg_online(fg);
186 		if (charging <= 0) {
187 			printf("Not charging, online=%d. Shutdown...\n",
188 			       charging);
189 			/* wait uart flush before shutdown */
190 			mdelay(500);
191 			/* PMIC shutdown */
192 			pmic_shutdown(pmic);
193 
194 			printf("Cpu should never reach here, shutdown failed !\n");
195 			continue;
196 		}
197 
198 		/*
199 		 * Just for fuel gauge to update something important,
200 		 * including charge current, coulometer or other.
201 		 */
202 		soc = fuel_gauge_get_soc(fg);
203 		if (soc < 0 || soc > 100) {
204 			printf("get soc failed: %d\n", soc);
205 			continue;
206 		}
207 
208 		printf("Extrem low power, force charging... threshold=%dmv, now=%dmv\n",
209 		       pdata->low_power_voltage, voltage);
210 
211 		/* System suspend */
212 		system_suspend_enter(pdata);
213 
214 		/* Update voltage */
215 		voltage = fuel_gauge_get_voltage(fg);
216 		if (voltage < 0) {
217 			printf("get voltage failed: %d\n", voltage);
218 			continue;
219 		}
220 	}
221 
222 	return 0;
223 }
224 
225 static int charge_animation_show(struct udevice *dev)
226 {
227 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
228 	struct charge_animation_priv *priv = dev_get_priv(dev);
229 	const struct charge_image *image = priv->image;
230 	struct udevice *pmic = priv->pmic;
231 	struct udevice *fg = priv->fg;
232 	const char *preboot = env_get("preboot");
233 	int image_num = priv->image_num;
234 	bool ever_lowpower_screen_off = false;
235 	bool screen_on = true;
236 	ulong show_start = 0, charge_start = 0, debug_start = 0;
237 	ulong ms = 0, sec = 0;
238 	int start_idx = 0, show_idx = -1;
239 	int soc, voltage, current, key_state;
240 	int i, charging = 1, ret;
241 	int boot_mode;
242 
243 /*
244  * Check sequence:
245  *
246  * 1. Extrem low power charge?
247  * 2. Preboot cmd?
248  * 3. Valid boot mode?
249  * 4. U-Boot charge enabled by dts config?
250  * 5. Screen off before charge?
251  * 6. Enter charge !
252  *
253  */
254 	/* Extrem low power charge */
255 	ret = charge_extrem_low_power(dev);
256 	if (ret < 0) {
257 		printf("extrem low power charge failed, ret=%d\n", ret);
258 		return ret;
259 	}
260 
261 	/* If there is preboot command, exit */
262 	if (preboot) {
263 		debug("preboot: %s\n", preboot);
264 		return 0;
265 	}
266 
267 	/* Not valid charge mode, exit */
268 #ifdef CONFIG_RKIMG_BOOTLOADER
269 	boot_mode = rockchip_get_boot_mode();
270 	if ((boot_mode != BOOT_MODE_CHARGING) &&
271 	    (boot_mode != BOOT_MODE_UNDEFINE)) {
272 		debug("exit charge, due to boot mode: %d\n", boot_mode);
273 		return 0;
274 	}
275 #endif
276 
277 	/* Enter android charge, set property for kernel */
278 	if (pdata->android_charge) {
279 		env_update("bootargs", "androidboot.mode=charger");
280 		printf("Android charge mode\n");
281 	}
282 
283 	/* Not enable U-Boot charge, exit */
284 	if (!pdata->uboot_charge)
285 		return 0;
286 
287 	/* Not charger online, exit */
288 	charging = fuel_gauge_get_chrg_online(fg);
289 	if (charging <= 0)
290 		return 0;
291 
292 	voltage = fuel_gauge_get_voltage(fg);
293 	if (voltage < 0) {
294 		printf("get voltage failed: %d\n", voltage);
295 		return -EINVAL;
296 	}
297 
298 	/* If low power, turn off screen */
299 	if (voltage <= pdata->screen_on_voltage + 50) {
300 		screen_on = false;
301 		ever_lowpower_screen_off = true;
302 		charge_show_bmp(NULL);
303 	}
304 
305 	printf("Enter U-Boot charging mode\n");
306 
307 	charge_start = get_timer(0);
308 	/* Charging ! */
309 	while (1) {
310 		debug("step1 (%d)... \n", screen_on);
311 
312 		/* Step1: Is charging now ? */
313 		charging = fuel_gauge_get_chrg_online(fg);
314 		if (charging <= 0) {
315 			printf("Not charging, online=%d. Shutdown...\n",
316 			       charging);
317 
318 			/* wait uart flush before shutdown */
319 			mdelay(500);
320 
321 			/* PMIC shutdown */
322 			pmic_shutdown(pmic);
323 
324 			printf("Cpu should never reach here, shutdown failed !\n");
325 			continue;
326 		}
327 
328 		debug("step2 (%d)... show_idx=%d\n", screen_on, show_idx);
329 
330 		/* Step2: get soc and voltage */
331 		soc = fuel_gauge_get_soc(fg);
332 		if (soc < 0 || soc > 100) {
333 			printf("get soc failed: %d\n", soc);
334 			continue;
335 		}
336 
337 		voltage = fuel_gauge_get_voltage(fg);
338 		if (voltage < 0) {
339 			printf("get voltage failed: %d\n", voltage);
340 			continue;
341 		}
342 
343 		current = fuel_gauge_get_current(fg);
344 		if (current == -ENOSYS) {
345 			printf("get current failed: %d\n", current);
346 			continue;
347 		}
348 
349 		/*
350 		 * Just for debug, otherwise there will be nothing output which
351 		 * is not good to know what happen.
352 		 */
353 		if (!debug_start)
354 			debug_start = get_timer(0);
355 		if (get_timer(debug_start) > 20000) {
356 			debug_start = get_timer(0);
357 			printf("[%8ld]: soc=%d%%, vol=%dmv, c=%dma, online=%d, screen_on=%d\n",
358 			       get_timer(0)/1000, soc, voltage,
359 			       current, charging, screen_on);
360 		}
361 
362 		/*
363 		 * If ever lowpower screen off, force screen_on=false, which
364 		 * means key event can't modify screen_on, only voltage higher
365 		 * then threshold can update screen_on=true;
366 		 */
367 		if (ever_lowpower_screen_off)
368 			screen_on = false;
369 
370 		/*
371 		 * Auto turn on screen when voltage higher than Vol screen on.
372 		 * 'ever_lowpower_screen_off' means enter while loop with
373 		 * screen off.
374 		 */
375 		if ((ever_lowpower_screen_off) &&
376 		    (voltage > pdata->screen_on_voltage)) {
377 			ever_lowpower_screen_off = false;
378 			screen_on = true;
379 			show_idx = IMAGE_SHOW_RESET;
380 		}
381 
382 		/*
383 		 * IMAGE_SHOW_RESET means show_idx show be update by start_idx.
384 		 * When short key pressed event trigged, we will set show_idx
385 		 * as IMAGE_SHOW_RESET which updates images index from start_idx
386 		 * that calculate by current soc.
387 		 */
388 		if (show_idx == IMAGE_SHOW_RESET) {
389 			for (i = 0; i < image_num - 2; i++) {
390 				/* Find out which image we start to show */
391 				if ((soc >= image[i].soc) &&
392 				    (soc < image[i + 1].soc)) {
393 					start_idx = i;
394 					break;
395 				}
396 
397 				if (soc >= 100) {
398 					start_idx = image_num - 2;
399 					break;
400 				}
401 			}
402 
403 			debug("%s: show_idx=%d, screen_on=%d\n",
404 			      __func__, show_idx, screen_on);
405 
406 			/* Mark start index and start time */
407 			show_idx = start_idx;
408 			show_start = get_timer(0);
409 		}
410 
411 		debug("step3 (%d)... show_idx=%d\n", screen_on, show_idx);
412 
413 		/* Step3: show images */
414 		if (screen_on) {
415 			debug("SHOW: %s\n", image[show_idx].name);
416 			charge_show_bmp(image[show_idx].name);
417 		} else {
418 			system_suspend_enter(pdata);
419 		}
420 
421 		mdelay(5);
422 
423 		/* Every image shows period */
424 		if (get_timer(show_start) > image[show_idx].period) {
425 			show_start = get_timer(0);
426 			/* Update to next image */
427 			show_idx++;
428 			if (show_idx > (image_num - 2))
429 				show_idx = IMAGE_SHOW_RESET;
430 		}
431 
432 		debug("step4 (%d)... \n", screen_on);
433 
434 		/*
435 		 * Step4: check key event.
436 		 *
437 		 * Short key event: turn on/off screen;
438 		 * Long key event: show logo and boot system or still charging.
439 		 */
440 		key_state = check_key_press();
441 		if (key_state == KEY_PRESS_DOWN) {
442 			/* NULL means show nothing, ie. turn off screen */
443 			if (screen_on)
444 				charge_show_bmp(NULL);
445 
446 			/*
447 			 * Clear current image index, and show image
448 			 * from start_idx
449 			 */
450 			show_idx = IMAGE_SHOW_RESET;
451 
452 			/*
453 			 * We turn off screen by charge_show_bmp(NULL), so we
454 			 * should tell while loop to stop show images any more.
455 			 *
456 			 * If screen_on=false, means this short key pressed
457 			 * event turn on the screen and we need show images.
458 			 *
459 			 * If screen_on=true, means this short key pressed
460 			 * event turn off the screen and we never show images.
461 			 */
462 			if (screen_on)
463 				screen_on = false;
464 			else
465 				screen_on = true;
466 		} else if (key_state == KEY_PRESS_LONG_DOWN) {
467 			/* Only long pressed while screen off needs screen_on true */
468 			if (!screen_on)
469 				screen_on = true;
470 
471 			/* Is able to boot now ? */
472 			if (soc < pdata->exit_charge_level) {
473 				printf("soc=%d%%, threshold soc=%d%%\n",
474 				       soc, pdata->exit_charge_level);
475 				printf("Low power, unable to boot, charging...\n");
476 				show_idx = image_num - 1;
477 				continue;
478 			}
479 
480 			if (voltage < pdata->exit_charge_voltage) {
481 				printf("voltage=%dmv, threshold voltage=%dmv\n",
482 				       voltage, pdata->exit_charge_voltage);
483 				printf("Low power, unable to boot, charging...\n");
484 				show_idx = image_num - 1;
485 				continue;
486 			}
487 
488 			/* Success exit charging */
489 			printf("Exit charge animation...\n");
490 			charge_show_logo();
491 			break;
492 		} else {
493 			/* Do nothing */
494 		}
495 
496 		debug("step5 (%d)... \n", screen_on);
497 
498 		/* Step5: Exit by ctrl+c */
499 		if (ctrlc()) {
500 			if (voltage >= pdata->screen_on_voltage)
501 				charge_show_logo();
502 			printf("Exit charge, due to ctrl+c\n");
503 			break;
504 		}
505 	}
506 
507 	ms = get_timer(charge_start);
508 	if (ms >= 1000) {
509 		sec = ms / 1000;
510 		ms = ms % 1000;
511 	}
512 
513 	printf("charging time total: %lu.%lus, soc=%d%%, vol=%dmv\n",
514 	       sec, ms, soc, voltage);
515 
516 	return 0;
517 }
518 
519 static const struct dm_charge_display_ops charge_animation_ops = {
520 	.show = charge_animation_show,
521 };
522 
523 static int charge_animation_probe(struct udevice *dev)
524 {
525 	struct charge_animation_priv *priv = dev_get_priv(dev);
526 	struct udevice *fg, *pmic;
527 	int ret, soc;
528 
529 	/* Get PMIC: used for power off system  */
530 	ret = uclass_get_device(UCLASS_PMIC, 0, &pmic);
531 	if (ret) {
532 		printf("Get UCLASS PMIC failed: %d\n", ret);
533 		return ret;
534 	}
535 	priv->pmic = pmic;
536 
537 	/* Get fuel gauge: used for charging */
538 	ret = uclass_get_device(UCLASS_FG, 0, &fg);
539 	if (ret) {
540 		printf("Get UCLASS FG failed: %d\n", ret);
541 		return ret;
542 	}
543 	priv->fg = fg;
544 
545 	/* Get PWRKEY: used for wakeup and trun off/on LCD */
546 	ret = platform_key_read(KEY_POWER);
547 	if (ret == KEY_NOT_EXIST) {
548 		printf("Can't find power key\n");
549 		return -EINVAL;
550 	}
551 
552 	/* Initialize charge current */
553 	soc = fuel_gauge_get_soc(fg);
554 	if (soc < 0 || soc > 100) {
555 		printf("get soc failed: %d\n", soc);
556 		return -EINVAL;
557 	}
558 
559 	/* Get charge images */
560 	priv->image = image;
561 	priv->image_num = ARRAY_SIZE(image);
562 
563 	printf("Enable charge animation display\n");
564 
565 	return 0;
566 }
567 
568 static const struct udevice_id charge_animation_ids[] = {
569 	{ .compatible = "rockchip,uboot-charge" },
570 	{ },
571 };
572 
573 U_BOOT_DRIVER(charge_animation) = {
574 	.name = "charge-animation",
575 	.id = UCLASS_CHARGE_DISPLAY,
576 	.probe = charge_animation_probe,
577 	.of_match = charge_animation_ids,
578 	.ops = &charge_animation_ops,
579 	.ofdata_to_platdata = charge_animation_ofdata_to_platdata,
580 	.platdata_auto_alloc_size = sizeof(struct charge_animation_pdata),
581 	.priv_auto_alloc_size = sizeof(struct charge_animation_priv),
582 };
583