xref: /rk3399_rockchip-uboot/drivers/power/charge_animation.c (revision 05b25c8bf30a77beb555abb8f65e78bd2f21b5de)
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 <dm.h>
9 #include <errno.h>
10 #include <key.h>
11 #include <linux/input.h>
12 #include <pwm.h>
13 #include <power/charge_display.h>
14 #include <power/fuel_gauge.h>
15 #include <power/pmic.h>
16 #include <power/rk8xx_pmic.h>
17 #include <power/regulator.h>
18 #include <video_rockchip.h>
19 
20 DECLARE_GLOBAL_DATA_PTR;
21 
22 #define LONG_PRESSED_TIME			2000 /* 2s */
23 #define IMAGE_SHOW_RESET			-1
24 
25 enum key_event {
26 	KEY_NOT_PRESSED = 0,
27 	KEY_SHORT_PRESSED,
28 	KEY_LONG_PRESSED,
29 };
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 	struct udevice *pwrkey;
41 	const struct charge_image *image;
42 	int image_num;
43 };
44 
45 struct charge_animation_pdata {
46 	int screen_on_voltage_threshold;
47 	int power_on_voltage_threshold;
48 	int power_on_soc_threshold;
49 	bool suspend_to_sram;
50 	bool auto_start_kernel;
51 };
52 
53 static int charge_animation_threshold_soc(struct udevice *dev)
54 {
55 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
56 
57 	if (!pdata)
58 		return -ENOSYS;
59 
60 	return pdata->power_on_soc_threshold;
61 }
62 
63 static int charge_animation_threshold_voltage(struct udevice *dev)
64 {
65 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
66 
67 	if (!pdata)
68 		return -ENOSYS;
69 
70 	return pdata->power_on_voltage_threshold;
71 }
72 
73 /*
74  * IF you want to use your own charge images, please:
75  *
76  * 1. Update the following 'image[]' to point to your own images;
77  * 2. You must set the failed image as last one and soc = -1 !!!
78  */
79 static const struct charge_image image[] = {
80 	{ .name = "battery_0.bmp", .soc = 5, .period = 600 },
81 	{ .name = "battery_1.bmp", .soc = 20, .period = 600 },
82 	{ .name = "battery_2.bmp", .soc = 40, .period = 600 },
83 	{ .name = "battery_3.bmp", .soc = 60, .period = 600 },
84 	{ .name = "battery_4.bmp", .soc = 80, .period = 600 },
85 	{ .name = "battery_5.bmp", .soc = 100, .period = 600 },
86 	{ .name = "battery_fail.bmp", .soc = -1, .period = 1000 },
87 };
88 
89 static int charge_animation_ofdata_to_platdata(struct udevice *dev)
90 {
91 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
92 
93 	if (dev_read_bool(dev, "charge,suspend-to-sram"))
94 		pdata->suspend_to_sram = true;
95 	else
96 		pdata->suspend_to_sram = false;
97 
98 	if (dev_read_bool(dev, "charge,auto-start-kernel"))
99 		pdata->auto_start_kernel = true;
100 	else
101 		pdata->auto_start_kernel = false;
102 
103 	pdata->power_on_soc_threshold =
104 		dev_read_u32_default(dev, "power-on-soc-threshold", 0);
105 	pdata->power_on_voltage_threshold =
106 		dev_read_u32_default(dev, "power-on-voltage-threshold", 0);
107 	pdata->screen_on_voltage_threshold =
108 		dev_read_u32_default(dev, "screen-on-voltage-threshold", 0);
109 
110 	if (pdata->screen_on_voltage_threshold >
111 	    pdata->power_on_voltage_threshold)
112 		pdata->screen_on_voltage_threshold =
113 					pdata->power_on_voltage_threshold;
114 
115 	debug("threshold soc=%d%%, voltage=%dmv, screen_on=%dmv, suspend=%d\n",
116 	      pdata->power_on_soc_threshold, pdata->power_on_voltage_threshold,
117 	      pdata->screen_on_voltage_threshold, pdata->suspend_to_sram);
118 
119 	return 0;
120 }
121 
122 static int check_key_press(struct udevice *dev, bool restar_time)
123 {
124 	static unsigned long pressed_time;
125 	static int old_state;
126 	int report = KEY_NOT_PRESSED;
127 	int state;
128 
129 	/* just for restart time */
130 	if (restar_time) {
131 		pressed_time = get_timer(0);
132 		goto out;
133 	}
134 
135 	state = key_read(dev);
136 	if (state < 0) {
137 		printf("read power key failed: %d\n", state);
138 		goto out;
139 	}
140 
141 	/* Start time is not initialized, let's do it */
142 	if (!pressed_time && (state == KEY_PRESS_DOWN)) {
143 		pressed_time = get_timer(0);
144 		return KEY_NOT_PRESSED;
145 	} else {
146 		debug("key state = %d\n", state);
147 
148 		if (state == KEY_PRESS_DOWN) {
149 			if (get_timer(pressed_time) >= LONG_PRESSED_TIME) {
150 				report = KEY_LONG_PRESSED;
151 				pressed_time = 0;
152 				old_state = KEY_LONG_PRESSED;
153 				printf("power key long pressed...\n");
154 			}
155 		/*
156 		 * If you don't check 'old_state != KEY_LONG_PRESSED', it will
157 		 * treat 'key long pressed' release as a 'short key pressed'
158 		 * release.
159 		 */
160 		} else if ((state == KEY_PRESS_UP) &&
161 			   (old_state != KEY_LONG_PRESSED)) {
162 			report = KEY_SHORT_PRESSED;
163 			old_state = report;
164 			pressed_time = 0;
165 			printf("power key short pressed...\n");
166 		} else {
167 			report = KEY_NOT_PRESSED;
168 			old_state = report;
169 			pressed_time = 0;
170 			debug("power key Not pressed...\n");
171 		}
172 	}
173 
174 out:
175 	return report;
176 }
177 
178 static int charge_animation_show(struct udevice *dev)
179 {
180 	struct charge_animation_pdata *pdata = dev_get_platdata(dev);
181 	struct charge_animation_priv *priv = dev_get_priv(dev);
182 	const struct charge_image *image = priv->image;
183 	struct udevice *pwrkey = priv->pwrkey;
184 	struct udevice *pmic = priv->pmic;
185 	struct udevice *fg = priv->fg;
186 	int image_num = priv->image_num;
187 	bool ever_lowpower_screen_off = false;
188 	bool screen_on = true;
189 	ulong show_start = 0, charge_start = 0, debug_start = 0;
190 	ulong ms = 0, sec = 0;
191 	int start_idx = 0, show_idx = -1;
192 	int soc, voltage, key_state;
193 	int i, charging = 1;
194 
195 	/* Not charger online, exit */
196 	charging = fuel_gauge_get_chrg_online(fg);
197 	if (charging <= 0)
198 		return 0;
199 
200 	voltage = fuel_gauge_get_voltage(fg);
201 	if (voltage < 0) {
202 		printf("get voltage failed: %d\n", voltage);
203 		return -EINVAL;
204 	}
205 
206 	/* If low power, turn off screen */
207 	if (voltage <= pdata->screen_on_voltage_threshold + 50) {
208 		screen_on = false;
209 		ever_lowpower_screen_off = true;
210 		rockchip_show_bmp(NULL);
211 	}
212 
213 	charge_start = get_timer(0);
214 
215 	/* Charging ! */
216 	while (1) {
217 		debug("step1 (%d)... \n", screen_on);
218 
219 		/* Step1: Is charging now ? */
220 		charging = fuel_gauge_get_chrg_online(fg);
221 		if (charging <= 0) {
222 			printf("Not charging, online=%d. Shutdown...\n",
223 			       charging);
224 
225 			/* wait uart flush before shutdown */
226 			mdelay(500);
227 
228 			/* PMIC shutdown */
229 			pmic_shutdown(pmic);
230 
231 			printf("Cpu should never reach here, shutdown failed !\n");
232 			continue;
233 		}
234 
235 		debug("step2 (%d)... show_idx=%d\n", screen_on, show_idx);
236 
237 		/* Step2: get soc and voltage */
238 		soc = fuel_gauge_get_soc(fg);
239 		if (soc < 0 || soc > 100) {
240 			printf("get soc failed: %d\n", soc);
241 			continue;
242 		}
243 
244 		voltage = fuel_gauge_get_voltage(fg);
245 		if (voltage < 0) {
246 			printf("get voltage failed: %d\n", voltage);
247 			continue;
248 		}
249 
250 		/*
251 		 * Just for debug, otherwise there will be nothing output which
252 		 * is not good to know what happen.
253 		 */
254 		if (!debug_start)
255 			debug_start = get_timer(0);
256 		if (get_timer(debug_start) > 20000) {
257 			debug_start = get_timer(0);
258 			printf("soc=%d, vol=%d, online=%d, screen_on=%d, show_idx=%d, ever_off=%d\n",
259 			       soc, voltage, charging, screen_on, show_idx,
260 			       ever_lowpower_screen_off);
261 		}
262 
263 		/*
264 		 * If ever lowpower screen off, force screen on false, which
265 		 * means key event can't modify screen_on, only voltage higher
266 		 * then threshold can update screen_on=true;
267 		 */
268 		if (ever_lowpower_screen_off)
269 			screen_on = false;
270 
271 		/*
272 		 * Auto turn on screen when voltage higher than Vol screen on.
273 		 * 'ever_lowpower_screen_off' means enter while loop with
274 		 * screen off.
275 		 */
276 		if ((ever_lowpower_screen_off) &&
277 		    (voltage > pdata->screen_on_voltage_threshold)) {
278 			ever_lowpower_screen_off = false;
279 			screen_on = true;
280 			show_idx = IMAGE_SHOW_RESET;
281 		}
282 
283 		/*
284 		 * IMAGE_SHOW_RESET means show_idx show be update by start_idx.
285 		 * When short key pressed event trigged, we will set show_idx
286 		 * as IMAGE_SHOW_RESET which updates images index from start_idx
287 		 * that calculate by current soc.
288 		 */
289 		if (show_idx == IMAGE_SHOW_RESET) {
290 			for (i = 0; i < image_num - 2; i++) {
291 				/* Find out which image we start to show */
292 				if ((soc >= image[i].soc) &&
293 				    (soc < image[i + 1].soc)) {
294 					start_idx = i;
295 					break;
296 				}
297 
298 				if (soc >= 100) {
299 					start_idx = image_num - 2;
300 					break;
301 				}
302 			}
303 
304 			debug("%s: show_idx=%d, screen_on=%d\n",
305 			      __func__, show_idx, screen_on);
306 
307 			/* Mark start index and start time */
308 			show_idx = start_idx;
309 			show_start = get_timer(0);
310 		}
311 
312 		debug("step3 (%d)... show_idx=%d\n", screen_on, show_idx);
313 
314 		/* Step3: show images */
315 		if (screen_on) {
316 			debug("SHOW: %s\n", image[show_idx].name);
317 			rockchip_show_bmp(image[show_idx].name);
318 		} else {
319 			/*
320 			 * TODO: enter low power mode:
321 			 *
322 			 * 1. cut off some regualtors;
323 			 * 2. set 24MHZ for some PLLs;
324 			 * 3. auto turn off screen when timout;
325 			 * 4. power key wakeup;
326 			 * 5. timer period wakeup for pmic fg ?
327 			 */
328 		}
329 
330 		mdelay(5);
331 
332 		/* Every image shows period */
333 		if (get_timer(show_start) > image[show_idx].period) {
334 			show_start = get_timer(0);
335 			/* Update to next image */
336 			show_idx++;
337 			if (show_idx > (image_num - 2))
338 				show_idx = IMAGE_SHOW_RESET;
339 		}
340 
341 		debug("step4 (%d)... \n", screen_on);
342 
343 		/*
344 		 * Step4: check key event.
345 		 *
346 		 * Short key event: turn on/off screen;
347 		 * Long key event: show logo and boot system or still charging.
348 		 */
349 		key_state = check_key_press(pwrkey, false);
350 		if (key_state == KEY_SHORT_PRESSED) {
351 			/* NULL means show nothing, ie. turn off screen */
352 			if (screen_on)
353 				rockchip_show_bmp(NULL);
354 
355 			/*
356 			 * Clear current image index, and show image
357 			 * from start_idx
358 			 */
359 			show_idx = IMAGE_SHOW_RESET;
360 
361 			/*
362 			 * We turn off screen by rockchip_show_bmp(NULL), so we
363 			 * should tell while loop to stop show images any more.
364 			 *
365 			 * If screen_on=false, means this short key pressed
366 			 * event turn on the screen and we need show images.
367 			 *
368 			 * If screen_on=true, means this short key pressed
369 			 * event turn off the screen and we never show images.
370 			 */
371 			if (screen_on)
372 				screen_on = false;
373 			else
374 				screen_on = true;
375 		} else if (key_state == KEY_LONG_PRESSED) {
376 			/* Only long pressed while screen off needs screen_on true */
377 			if (!screen_on)
378 				screen_on = true;
379 
380 			/* Is able to boot now ? */
381 			if (soc < pdata->power_on_soc_threshold) {
382 				printf("soc=%d%%, threshold soc=%d%%\n",
383 				       soc, pdata->power_on_soc_threshold);
384 				printf("Low power, unable to boot, charging...\n");
385 				/* 'true': just for clear time of check key */
386 				check_key_press(pwrkey, true);
387 				show_idx = image_num - 1;
388 				continue;
389 			}
390 
391 			if (voltage < pdata->power_on_voltage_threshold) {
392 				printf("voltage=%dmv, threshold voltage=%dmv\n",
393 				       voltage, pdata->power_on_voltage_threshold);
394 				printf("Low power, unable to boot, charging...\n");
395 				/* 'true': just for clear time of check key */
396 				check_key_press(pwrkey, true);
397 				show_idx = image_num - 1;
398 				continue;
399 			}
400 
401 			/* Success exit charging */
402 			printf("Exit charge animation...\n");
403 			rockchip_show_logo();
404 			break;
405 		} else {
406 			/* Do nothing */
407 		}
408 
409 		debug("step5 (%d)... \n", screen_on);
410 
411 		/*
412 		 * Step5: Check auto start kernel
413 		 */
414 		if (pdata->auto_start_kernel) {
415 			if ((voltage >= pdata->power_on_voltage_threshold) &&
416 			    (soc >= pdata->power_on_soc_threshold)) {
417 				printf("Auto start, exit charge animation..\n");
418 				rockchip_show_logo();
419 				break;
420 			}
421 		}
422 	}
423 
424 	ms = get_timer(charge_start);
425 	if (ms >= 1000) {
426 		sec = ms / 1000;
427 		ms = ms % 1000;
428 	}
429 
430 	printf("charging time total: %lu.%lus, soc=%d%%, vol=%dmv\n",
431 	       sec, ms, soc, voltage);
432 
433 	return 0;
434 }
435 
436 static const struct dm_charge_display_ops charge_animation_ops = {
437 	.get_power_on_soc = charge_animation_threshold_soc,
438 	.get_power_on_voltage = charge_animation_threshold_voltage,
439 	.show = charge_animation_show,
440 };
441 
442 static int charge_animation_probe(struct udevice *dev)
443 {
444 	struct charge_animation_priv *priv = dev_get_priv(dev);
445 	struct udevice *pwrkey, *fg, *pmic;
446 	int ret;
447 
448 	/* Get PMIC */
449 	ret = uclass_get_device(UCLASS_PMIC, 0, &pmic);
450 	if (ret) {
451 		printf("Get UCLASS PMIC failed: %d\n", ret);
452 		return ret;
453 	}
454 	priv->pmic = pmic;
455 
456 	/* Get power key */
457 	for (uclass_first_device(UCLASS_KEY, &pwrkey);
458 	     pwrkey;
459 	     uclass_next_device(&pwrkey)) {
460 		if (key_type(pwrkey) == KEY_POWER) {
461 			priv->pwrkey = pwrkey;
462 			break;
463 		}
464 	}
465 	if (!priv->pwrkey) {
466 		printf("Can't find any power key\n");
467 		return -ENOSYS;
468 	}
469 
470 	/* Get fuel gauge */
471 	ret = uclass_get_device(UCLASS_FG, 0, &fg);
472 	if (ret) {
473 		printf("Get UCLASS FG failed: %d\n", ret);
474 		return ret;
475 	}
476 	priv->fg = fg;
477 
478 	/* Get image */
479 	priv->image = image;
480 	priv->image_num = ARRAY_SIZE(image);
481 
482 	printf("Enable charge animation display\n");
483 
484 	return 0;
485 }
486 
487 static const struct udevice_id charge_animation_ids[] = {
488 	{ .compatible = "charge-animation" },
489 	{ },
490 };
491 
492 U_BOOT_DRIVER(charge_animation) = {
493 	.name = "charge-animation",
494 	.id = UCLASS_CHARGE_DISPLAY,
495 	.probe = charge_animation_probe,
496 	.of_match = charge_animation_ids,
497 	.ops = &charge_animation_ops,
498 	.ofdata_to_platdata = charge_animation_ofdata_to_platdata,
499 	.platdata_auto_alloc_size = sizeof(struct charge_animation_pdata),
500 	.priv_auto_alloc_size = sizeof(struct charge_animation_priv),
501 };
502