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 379116e2f9SJoseph Chen #define IMAGE_RECALC_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 85d07906f0SJoseph Chen static int regulators_parse_assigned_mem_state(struct udevice *dev) 86d07906f0SJoseph Chen { 87d07906f0SJoseph Chen struct charge_animation_pdata *pdata = dev_get_platdata(dev); 88d07906f0SJoseph Chen struct regulator_mem *mem; 89d07906f0SJoseph Chen const fdt32_t *list1; 90d07906f0SJoseph Chen const fdt32_t *list2; 91d07906f0SJoseph Chen int size1, size2; 92d07906f0SJoseph Chen int i, ret; 93d07906f0SJoseph Chen uint32_t phandle; 94d07906f0SJoseph Chen 95d07906f0SJoseph Chen /* Must be both exist or not */ 96d07906f0SJoseph Chen list1 = dev_read_prop(dev, "regulator-on-in-mem", &size1); 97d07906f0SJoseph Chen list2 = dev_read_prop(dev, "regulator-off-in-mem", &size2); 98d07906f0SJoseph Chen if (!list1 && !list2) 99d07906f0SJoseph Chen return 0; 100d07906f0SJoseph Chen if (list1 && !list2) 101d07906f0SJoseph Chen return -EINVAL; 102d07906f0SJoseph Chen else if (!list1 && list2) 103d07906f0SJoseph Chen return -EINVAL; 104d07906f0SJoseph Chen 105d07906f0SJoseph Chen size1 = size1 / sizeof(*list1); 106d07906f0SJoseph Chen size2 = size2 / sizeof(*list2); 107d07906f0SJoseph Chen 108d07906f0SJoseph Chen pdata->regulators_mem = 109d07906f0SJoseph Chen calloc(size1 + size2, sizeof(*pdata->regulators_mem)); 110d07906f0SJoseph Chen if (!pdata->regulators_mem) 111d07906f0SJoseph Chen return -ENOMEM; 112d07906f0SJoseph Chen 113d07906f0SJoseph Chen mem = pdata->regulators_mem; 114d07906f0SJoseph Chen 115d07906f0SJoseph Chen for (i = 0; i < size1; i++, mem++) { 116d07906f0SJoseph Chen mem->enable = true; 117d07906f0SJoseph Chen phandle = fdt32_to_cpu(*list1++); 118d07906f0SJoseph Chen ret = uclass_get_device_by_phandle_id(UCLASS_REGULATOR, 119d07906f0SJoseph Chen phandle, &mem->dev); 120d07906f0SJoseph Chen if (ret) 121d07906f0SJoseph Chen return ret; 122d07906f0SJoseph Chen } 123d07906f0SJoseph Chen for (i = 0; i < size2; i++, mem++) { 124d07906f0SJoseph Chen mem->enable = false; 125d07906f0SJoseph Chen phandle = fdt32_to_cpu(*list2++); 126d07906f0SJoseph Chen ret = uclass_get_device_by_phandle_id(UCLASS_REGULATOR, 127d07906f0SJoseph Chen phandle, &mem->dev); 128d07906f0SJoseph Chen if (ret) 129d07906f0SJoseph Chen return ret; 130d07906f0SJoseph Chen } 131d07906f0SJoseph Chen 132d07906f0SJoseph Chen #ifdef DEBUG 133d07906f0SJoseph Chen printf("assigned regulator mem:\n"); 134d07906f0SJoseph Chen for (mem = pdata->regulators_mem; mem->dev; mem++) 135d07906f0SJoseph Chen printf(" %20s: suspend %s\n", mem->dev->name, 136d07906f0SJoseph Chen mem->enable ? "enabling" : "disabled"); 137d07906f0SJoseph Chen #endif 138d07906f0SJoseph Chen return 0; 139d07906f0SJoseph Chen } 140d07906f0SJoseph Chen 141d07906f0SJoseph Chen static int regulators_enable_assigned_state_mem(struct udevice *dev) 142d07906f0SJoseph Chen { 143d07906f0SJoseph Chen struct charge_animation_pdata *pdata = dev_get_platdata(dev); 144d07906f0SJoseph Chen struct regulator_mem *mem; 145d07906f0SJoseph Chen int ret; 146d07906f0SJoseph Chen 147d07906f0SJoseph Chen for (mem = pdata->regulators_mem; mem->dev; mem++) { 148d07906f0SJoseph Chen ret = regulator_set_suspend_enable(mem->dev, mem->enable); 149d07906f0SJoseph Chen if (ret) 150d07906f0SJoseph Chen printf("%s: suspend failed, ret=%d\n", 151d07906f0SJoseph Chen mem->dev->name, ret); 152d07906f0SJoseph Chen } 153d07906f0SJoseph Chen 154d07906f0SJoseph Chen return 0; 155d07906f0SJoseph Chen } 156d07906f0SJoseph Chen 157d07906f0SJoseph Chen static void regulators_suspend(struct udevice *dev) 158d07906f0SJoseph Chen { 159d07906f0SJoseph Chen struct charge_animation_pdata *pdata = dev_get_platdata(dev); 160d07906f0SJoseph Chen 161d07906f0SJoseph Chen if (pdata->regulators_mem) 162d07906f0SJoseph Chen regulators_enable_assigned_state_mem(dev); 163d07906f0SJoseph Chen else 164d07906f0SJoseph Chen regulators_enable_state_mem(false); 165d07906f0SJoseph Chen } 166d07906f0SJoseph Chen 167*9c7e8d73SJoseph Chen static void pmics_ops(bool suspend) 168*9c7e8d73SJoseph Chen { 169*9c7e8d73SJoseph Chen struct udevice *dev; 170*9c7e8d73SJoseph Chen 171*9c7e8d73SJoseph Chen for (uclass_first_device(UCLASS_PMIC, &dev); 172*9c7e8d73SJoseph Chen dev; 173*9c7e8d73SJoseph Chen uclass_next_device(&dev)) { 174*9c7e8d73SJoseph Chen if (suspend) 175*9c7e8d73SJoseph Chen pmic_suspend(dev); 176*9c7e8d73SJoseph Chen else 177*9c7e8d73SJoseph Chen pmic_resume(dev); 178*9c7e8d73SJoseph Chen } 179*9c7e8d73SJoseph Chen } 180*9c7e8d73SJoseph Chen 181*9c7e8d73SJoseph Chen static void pmics_suspend(void) 182*9c7e8d73SJoseph Chen { 183*9c7e8d73SJoseph Chen pmics_ops(true); 184*9c7e8d73SJoseph Chen } 185*9c7e8d73SJoseph Chen 186*9c7e8d73SJoseph Chen static void pmics_resume(void) 187*9c7e8d73SJoseph Chen { 188*9c7e8d73SJoseph Chen pmics_ops(false); 189*9c7e8d73SJoseph Chen } 190*9c7e8d73SJoseph Chen 191f1c8ecceSJoseph Chen static int charge_animation_ofdata_to_platdata(struct udevice *dev) 192f1c8ecceSJoseph Chen { 193f1c8ecceSJoseph Chen struct charge_animation_pdata *pdata = dev_get_platdata(dev); 194f1c8ecceSJoseph Chen 195d6653c12SJoseph Chen /* charge mode */ 196d6653c12SJoseph Chen pdata->uboot_charge = 197d6653c12SJoseph Chen dev_read_u32_default(dev, "rockchip,uboot-charge-on", 0); 198d6653c12SJoseph Chen pdata->android_charge = 199d6653c12SJoseph Chen dev_read_u32_default(dev, "rockchip,android-charge-on", 0); 200f1c8ecceSJoseph Chen 201038c1ecaSShunqing Chen pdata->auto_exit_charge = 202038c1ecaSShunqing Chen dev_read_u32_default(dev, "rockchip,uboot-exit-charge-auto", 0); 203d6653c12SJoseph Chen pdata->exit_charge_level = 204d6653c12SJoseph Chen dev_read_u32_default(dev, "rockchip,uboot-exit-charge-level", 0); 205d6653c12SJoseph Chen pdata->exit_charge_voltage = 206d6653c12SJoseph Chen dev_read_u32_default(dev, "rockchip,uboot-exit-charge-voltage", 0); 207f23c35a8SJoseph Chen 208f23c35a8SJoseph Chen pdata->low_power_voltage = 209f23c35a8SJoseph Chen dev_read_u32_default(dev, "rockchip,uboot-low-power-voltage", 0); 210f23c35a8SJoseph Chen 211d6653c12SJoseph Chen pdata->screen_on_voltage = 212d6653c12SJoseph Chen dev_read_u32_default(dev, "rockchip,screen-on-voltage", 0); 213f23c35a8SJoseph Chen pdata->system_suspend = 214f23c35a8SJoseph Chen dev_read_u32_default(dev, "rockchip,system-suspend", 0); 215f1c8ecceSJoseph Chen 216e7f9facbSJoseph Chen pdata->auto_wakeup_interval = 217e7f9facbSJoseph Chen dev_read_u32_default(dev, "rockchip,auto-wakeup-interval", 0); 218e7f9facbSJoseph Chen pdata->auto_wakeup_screen_invert = 219e7f9facbSJoseph Chen dev_read_u32_default(dev, "rockchip,auto-wakeup-screen-invert", 0); 220e7f9facbSJoseph Chen 221e7f9facbSJoseph Chen pdata->auto_off_screen_interval = 222e7f9facbSJoseph Chen dev_read_u32_default(dev, "rockchip,auto-off-screen-interval", 15); 223e7f9facbSJoseph Chen 224f23c35a8SJoseph Chen if (pdata->screen_on_voltage > pdata->exit_charge_voltage) 225f23c35a8SJoseph Chen pdata->screen_on_voltage = pdata->exit_charge_voltage; 226f1c8ecceSJoseph Chen 227038c1ecaSShunqing Chen if (pdata->auto_exit_charge && !pdata->auto_wakeup_interval) 228038c1ecaSShunqing Chen pdata->auto_wakeup_interval = 10; 229038c1ecaSShunqing Chen 230d07906f0SJoseph Chen /* Not allow failure */ 231d07906f0SJoseph Chen if (regulators_parse_assigned_mem_state(dev)) { 232d07906f0SJoseph Chen printf("Failed to parse assigned mem state\n"); 233d07906f0SJoseph Chen return -EINVAL; 234d07906f0SJoseph Chen } 235d07906f0SJoseph Chen 236d6653c12SJoseph Chen debug("mode: uboot=%d, android=%d; exit: soc=%d%%, voltage=%dmv;\n" 237f23c35a8SJoseph Chen "lp_voltage=%d%%, screen_on=%dmv\n", 238d6653c12SJoseph Chen pdata->uboot_charge, pdata->android_charge, 239d6653c12SJoseph Chen pdata->exit_charge_level, pdata->exit_charge_voltage, 240f23c35a8SJoseph Chen pdata->low_power_voltage, pdata->screen_on_voltage); 241f1c8ecceSJoseph Chen 242f1c8ecceSJoseph Chen return 0; 243f1c8ecceSJoseph Chen } 244f1c8ecceSJoseph Chen 245e7f9facbSJoseph Chen static int check_key_press(struct udevice *dev) 246f1c8ecceSJoseph Chen { 247e7f9facbSJoseph Chen struct charge_animation_pdata *pdata = dev_get_platdata(dev); 248e7f9facbSJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 2499116e2f9SJoseph Chen u32 event; 2508b436ce5SElaine Zhang 2518b436ce5SElaine Zhang #ifdef CONFIG_DM_RTC 2529116e2f9SJoseph Chen if (priv->rtc && rtc_alarm_trigger(priv->rtc)) { 2538b436ce5SElaine Zhang printf("rtc alarm trigger...\n"); 2548b436ce5SElaine Zhang return KEY_PRESS_LONG_DOWN; 2558b436ce5SElaine Zhang } 2569116e2f9SJoseph Chen #endif 2579116e2f9SJoseph Chen event = key_read(KEY_POWER); 2589116e2f9SJoseph Chen if (event < 0) 2599116e2f9SJoseph Chen printf("read power key failed: %d\n", event); 2609116e2f9SJoseph Chen else if (event == KEY_PRESS_DOWN) 26193aee2d9SJoseph Chen printf("power key pressed...\n"); 2629116e2f9SJoseph Chen else if (event == KEY_PRESS_LONG_DOWN) 26393aee2d9SJoseph Chen printf("power key long pressed...\n"); 264f1c8ecceSJoseph Chen 2659116e2f9SJoseph Chen /* auto screen invert ? */ 2669116e2f9SJoseph Chen if (pdata->auto_wakeup_interval && 2679116e2f9SJoseph Chen pdata->auto_wakeup_screen_invert) { 268e7f9facbSJoseph Chen if (priv->auto_wakeup_key_state == KEY_PRESS_DOWN) { 269e7f9facbSJoseph Chen /* Value is updated in timer interrupt */ 270e7f9facbSJoseph Chen priv->auto_wakeup_key_state = KEY_PRESS_NONE; 2719116e2f9SJoseph Chen event = KEY_PRESS_DOWN; 272e7f9facbSJoseph Chen } 273e7f9facbSJoseph Chen } 2749116e2f9SJoseph Chen 2759116e2f9SJoseph Chen /* auto screen off (while not enable auto screen invert) ? */ 27623858492SShunqing Chen if (!pdata->auto_wakeup_screen_invert && 27723858492SShunqing Chen pdata->auto_off_screen_interval) { 27893aee2d9SJoseph Chen if (priv->auto_screen_off_timeout && 27993aee2d9SJoseph Chen get_timer(priv->auto_screen_off_timeout) > 280e7f9facbSJoseph Chen pdata->auto_off_screen_interval * 1000) { /* 1000ms */ 2819116e2f9SJoseph Chen event = KEY_PRESS_DOWN; 282e7f9facbSJoseph Chen printf("Auto screen off\n"); 283e7f9facbSJoseph Chen } 284e7f9facbSJoseph Chen } 285e7f9facbSJoseph Chen 2869116e2f9SJoseph Chen return event; 287f1c8ecceSJoseph Chen } 288f1c8ecceSJoseph Chen 289175257e4SJoseph Chen /* 290175257e4SJoseph Chen * If not enable CONFIG_IRQ, cpu can't suspend to ATF or wfi, so that wakeup 291175257e4SJoseph Chen * period timer is useless. 292175257e4SJoseph Chen */ 2933415d4ebSJoseph Chen #if !defined(CONFIG_IRQ) || !defined(CONFIG_ARM_CPU_SUSPEND) 2942e68f6b5SJoseph Chen static int system_suspend_enter(struct udevice *dev) 295175257e4SJoseph Chen { 296175257e4SJoseph Chen return 0; 297175257e4SJoseph Chen } 298175257e4SJoseph Chen 299175257e4SJoseph Chen static void autowakeup_timer_init(struct udevice *dev, uint32_t seconds) {} 300175257e4SJoseph Chen static void autowakeup_timer_uninit(void) {} 301175257e4SJoseph Chen 302175257e4SJoseph Chen #else 3032e68f6b5SJoseph Chen static int system_suspend_enter(struct udevice *dev) 304b177a917SJoseph Chen { 3052e68f6b5SJoseph Chen struct charge_animation_pdata *pdata = dev_get_platdata(dev); 3062e68f6b5SJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 3072e68f6b5SJoseph Chen 3082e68f6b5SJoseph Chen /* 3092e68f6b5SJoseph Chen * When cpu is in wfi and we try to give a long key press event without 3102e68f6b5SJoseph Chen * key release, cpu would wakeup and enter wfi again immediately. So 3112e68f6b5SJoseph Chen * here is the problem: cpu can only wakeup when long key released. 3122e68f6b5SJoseph Chen * 3132e68f6b5SJoseph Chen * Actually, we want cpu can detect long key event without key release, 3142e68f6b5SJoseph Chen * so we give a suspend delay timeout for cpu to detect this. 3152e68f6b5SJoseph Chen */ 3162e68f6b5SJoseph Chen if (priv->suspend_delay_timeout && 3172e68f6b5SJoseph Chen get_timer(priv->suspend_delay_timeout) <= SYSTEM_SUSPEND_DELAY_MS) 3182e68f6b5SJoseph Chen return 0; 3192e68f6b5SJoseph Chen 320f23c35a8SJoseph Chen if (pdata->system_suspend && IS_ENABLED(CONFIG_ARM_SMCCC)) { 321b177a917SJoseph Chen printf("\nSystem suspend: "); 322992f4e77SJoseph Chen putc('0'); 323b177a917SJoseph Chen local_irq_disable(); 32406b61291SJoseph Chen putc('1'); 325d07906f0SJoseph Chen regulators_suspend(dev); 326b177a917SJoseph Chen putc('2'); 327*9c7e8d73SJoseph Chen pmics_suspend(); 328b177a917SJoseph Chen putc('3'); 32906b61291SJoseph Chen irqs_suspend(); 33088949342SJoseph Chen putc('4'); 33106b61291SJoseph Chen device_suspend(); 33206b61291SJoseph Chen putc('5'); 333b177a917SJoseph Chen putc('\n'); 334b177a917SJoseph Chen 335b177a917SJoseph Chen /* Trap into ATF for low power mode */ 336b177a917SJoseph Chen cpu_suspend(0, psci_system_suspend); 337b177a917SJoseph Chen 338b177a917SJoseph Chen putc('\n'); 33988949342SJoseph Chen putc('4'); 34088949342SJoseph Chen device_resume(); 341b177a917SJoseph Chen putc('3'); 342b177a917SJoseph Chen irqs_resume(); 343b177a917SJoseph Chen putc('2'); 344*9c7e8d73SJoseph Chen pmics_resume(); 345b177a917SJoseph Chen putc('1'); 34606b61291SJoseph Chen local_irq_enable(); 34706b61291SJoseph Chen putc('0'); 348b177a917SJoseph Chen putc('\n'); 3498fc5ae06SJoseph Chen } else { 3505eac14dbSJoseph Chen irqs_suspend(); 3518fc5ae06SJoseph Chen printf("\nWfi\n"); 3528fc5ae06SJoseph Chen wfi(); 3532e68f6b5SJoseph Chen putc('1'); 3545eac14dbSJoseph Chen irqs_resume(); 3558fc5ae06SJoseph Chen } 356b177a917SJoseph Chen 3572e68f6b5SJoseph Chen priv->suspend_delay_timeout = get_timer(0); 3582e68f6b5SJoseph Chen 359b177a917SJoseph Chen /* 360b177a917SJoseph Chen * We must wait for key release event finish, otherwise 361b177a917SJoseph Chen * we may read key state too early. 362b177a917SJoseph Chen */ 363b177a917SJoseph Chen mdelay(300); 364b177a917SJoseph Chen 365b177a917SJoseph Chen return 0; 366f1c8ecceSJoseph Chen } 367ebe3d004SJoseph Chen 3689116e2f9SJoseph Chen static void autowake_timer_handler(int irq, void *data) 36979244e4cSJoseph Chen { 37079244e4cSJoseph Chen struct udevice *dev = data; 37179244e4cSJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 37279244e4cSJoseph Chen static long long count; 37379244e4cSJoseph Chen 37479244e4cSJoseph Chen writel(TIMER_CLR_INT, TIMER_BASE + TIMER_INTSTATUS); 37579244e4cSJoseph Chen 37679244e4cSJoseph Chen priv->auto_wakeup_key_state = KEY_PRESS_DOWN; 37779244e4cSJoseph Chen printf("auto wakeup count: %lld\n", ++count); 37879244e4cSJoseph Chen } 37979244e4cSJoseph Chen 38079244e4cSJoseph Chen static void autowakeup_timer_init(struct udevice *dev, uint32_t seconds) 38179244e4cSJoseph Chen { 38279244e4cSJoseph Chen uint64_t period = 24000000ULL * seconds; 38379244e4cSJoseph Chen 38479244e4cSJoseph Chen /* Disable before conifg */ 38579244e4cSJoseph Chen writel(0, TIMER_BASE + TIMER_CTRL); 38679244e4cSJoseph Chen 38779244e4cSJoseph Chen /* Config */ 38879244e4cSJoseph Chen writel((uint32_t)period, TIMER_BASE + TIMER_LOAD_COUNT0); 38979244e4cSJoseph Chen writel((uint32_t)(period >> 32), TIMER_BASE + TIMER_LOAD_COUNT1); 39079244e4cSJoseph Chen writel(TIMER_CLR_INT, TIMER_BASE + TIMER_INTSTATUS); 39179244e4cSJoseph Chen writel(TIMER_EN | TIMER_INT_EN, TIMER_BASE + TIMER_CTRL); 39279244e4cSJoseph Chen 39379244e4cSJoseph Chen /* IRQ */ 3949116e2f9SJoseph Chen irq_install_handler(TIMER_IRQ, autowake_timer_handler, dev); 39579244e4cSJoseph Chen irq_handler_enable(TIMER_IRQ); 39679244e4cSJoseph Chen } 39779244e4cSJoseph Chen 39879244e4cSJoseph Chen static void autowakeup_timer_uninit(void) 39979244e4cSJoseph Chen { 400094465a9SJoseph Chen writel(0, TIMER_BASE + TIMER_CTRL); 401094465a9SJoseph Chen 402094465a9SJoseph Chen irq_handler_disable(TIMER_IRQ); 40379244e4cSJoseph Chen irq_free_handler(TIMER_IRQ); 40479244e4cSJoseph Chen } 405175257e4SJoseph Chen #endif 40679244e4cSJoseph Chen 407f23c35a8SJoseph Chen #ifdef CONFIG_DRM_ROCKCHIP 408f23c35a8SJoseph Chen static void charge_show_bmp(const char *name) 409f23c35a8SJoseph Chen { 410f23c35a8SJoseph Chen rockchip_show_bmp(name); 411f23c35a8SJoseph Chen } 412f23c35a8SJoseph Chen 413f23c35a8SJoseph Chen static void charge_show_logo(void) 414f23c35a8SJoseph Chen { 415f23c35a8SJoseph Chen rockchip_show_logo(); 416f23c35a8SJoseph Chen } 417f23c35a8SJoseph Chen #else 418f23c35a8SJoseph Chen static void charge_show_bmp(const char *name) {} 419f23c35a8SJoseph Chen static void charge_show_logo(void) {} 420f23c35a8SJoseph Chen #endif 421f23c35a8SJoseph Chen 4221b3009deSJoseph Chen #ifdef CONFIG_LED 4231b3009deSJoseph Chen static int leds_update(struct udevice *dev, int soc) 4241b3009deSJoseph Chen { 4251b3009deSJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 4261b3009deSJoseph Chen static int old_soc = -1; 4271b3009deSJoseph Chen int ret, ledst; 4281b3009deSJoseph Chen 4291b3009deSJoseph Chen if (old_soc == soc) 4301b3009deSJoseph Chen return 0; 4311b3009deSJoseph Chen 4321b3009deSJoseph Chen old_soc = soc; 4331b3009deSJoseph Chen if (priv->led_charging) { 4341b3009deSJoseph Chen ledst = (soc < 100) ? LEDST_ON : LEDST_OFF; 4351b3009deSJoseph Chen ret = led_set_state(priv->led_charging, ledst); 4361b3009deSJoseph Chen if (ret) { 4371b3009deSJoseph Chen printf("set charging led %s failed, ret=%d\n", 4381b3009deSJoseph Chen (ledst == LEDST_ON) ? "ON" : "OFF", ret); 4391b3009deSJoseph Chen return ret; 4401b3009deSJoseph Chen } 4411b3009deSJoseph Chen } 4421b3009deSJoseph Chen 4431b3009deSJoseph Chen if (priv->led_full) { 4441b3009deSJoseph Chen ledst = (soc == 100) ? LEDST_ON : LEDST_OFF; 4451b3009deSJoseph Chen ret = led_set_state(priv->led_full, ledst); 4461b3009deSJoseph Chen if (ret) { 4471b3009deSJoseph Chen printf("set charging full led %s failed, ret=%d\n", 4481b3009deSJoseph Chen ledst == LEDST_ON ? "ON" : "OFF", ret); 4491b3009deSJoseph Chen return ret; 4501b3009deSJoseph Chen } 4511b3009deSJoseph Chen } 4521b3009deSJoseph Chen 4531b3009deSJoseph Chen return 0; 4541b3009deSJoseph Chen } 4551b3009deSJoseph Chen #else 4561b3009deSJoseph Chen static int leds_update(struct udevice *dev, int soc) { return 0; } 4571b3009deSJoseph Chen #endif 4581b3009deSJoseph Chen 4594d083e3fSJoseph Chen static int fg_charger_get_chrg_online(struct udevice *dev) 4604d083e3fSJoseph Chen { 4614d083e3fSJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 4624d083e3fSJoseph Chen struct udevice *charger; 4634d083e3fSJoseph Chen 4644d083e3fSJoseph Chen charger = priv->charger ? : priv->fg; 4654d083e3fSJoseph Chen 4664d083e3fSJoseph Chen return fuel_gauge_get_chrg_online(charger); 4674d083e3fSJoseph Chen } 4684d083e3fSJoseph Chen 469ceb76aadSJoseph Chen static int sys_shutdown(struct udevice *dev) 470ceb76aadSJoseph Chen { 471ceb76aadSJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 472ceb76aadSJoseph Chen struct udevice *pmic = priv->pmic; 473ceb76aadSJoseph Chen struct udevice *fg = priv->fg; 474ceb76aadSJoseph Chen 475ceb76aadSJoseph Chen /* 476ceb76aadSJoseph Chen * Call the fuel/charge again to update something specific 477ceb76aadSJoseph Chen * before shutdown. This fix a scene: 478ceb76aadSJoseph Chen * 479ceb76aadSJoseph Chen * Plug out charger which auto wakeup cpu from a long time system suspend, 480ceb76aadSJoseph Chen * fuel/charge need to update something before shutdown. 481ceb76aadSJoseph Chen */ 482ceb76aadSJoseph Chen fg_charger_get_chrg_online(dev); 483ceb76aadSJoseph Chen fuel_gauge_get_voltage(fg); 484ceb76aadSJoseph Chen fuel_gauge_update_get_soc(fg); 485ceb76aadSJoseph Chen 486ceb76aadSJoseph Chen flushc(); 487ceb76aadSJoseph Chen mdelay(50); 488ceb76aadSJoseph Chen pmic_shutdown(pmic); 489ceb76aadSJoseph Chen 490ceb76aadSJoseph Chen mdelay(500); 491ceb76aadSJoseph Chen printf("Cpu should never reach here, shutdown failed !\n"); 492ceb76aadSJoseph Chen 493ceb76aadSJoseph Chen return 0; 494ceb76aadSJoseph Chen } 495ceb76aadSJoseph Chen 496f23c35a8SJoseph Chen static int charge_extrem_low_power(struct udevice *dev) 497f23c35a8SJoseph Chen { 498f23c35a8SJoseph Chen struct charge_animation_pdata *pdata = dev_get_platdata(dev); 499f23c35a8SJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 500f23c35a8SJoseph Chen struct udevice *fg = priv->fg; 501f23c35a8SJoseph Chen int voltage, soc, charging = 1; 50279244e4cSJoseph Chen static int timer_initialized; 503f23c35a8SJoseph Chen 504f23c35a8SJoseph Chen voltage = fuel_gauge_get_voltage(fg); 505f23c35a8SJoseph Chen if (voltage < 0) 506f23c35a8SJoseph Chen return -EINVAL; 507f23c35a8SJoseph Chen 508f23c35a8SJoseph Chen while (voltage < pdata->low_power_voltage + 50) { 509f23c35a8SJoseph Chen /* Check charger online */ 5104d083e3fSJoseph Chen charging = fg_charger_get_chrg_online(dev); 511f23c35a8SJoseph Chen if (charging <= 0) { 51279244e4cSJoseph Chen printf("%s: Not charging, online=%d. Shutdown...\n", 51379244e4cSJoseph Chen __func__, charging); 514ceb76aadSJoseph Chen sys_shutdown(dev); 515f23c35a8SJoseph Chen continue; 516f23c35a8SJoseph Chen } 517f23c35a8SJoseph Chen 51879244e4cSJoseph Chen /* Enable auto wakeup */ 51979244e4cSJoseph Chen if (!timer_initialized) { 52079244e4cSJoseph Chen timer_initialized = 1; 52179244e4cSJoseph Chen autowakeup_timer_init(dev, 5); 52279244e4cSJoseph Chen } 52379244e4cSJoseph Chen 524f23c35a8SJoseph Chen /* 525f23c35a8SJoseph Chen * Just for fuel gauge to update something important, 526f23c35a8SJoseph Chen * including charge current, coulometer or other. 527f23c35a8SJoseph Chen */ 5284f9cab27SJoseph Chen soc = fuel_gauge_update_get_soc(fg); 529f23c35a8SJoseph Chen if (soc < 0 || soc > 100) { 530f23c35a8SJoseph Chen printf("get soc failed: %d\n", soc); 531f23c35a8SJoseph Chen continue; 532f23c35a8SJoseph Chen } 533f23c35a8SJoseph Chen 5341b3009deSJoseph Chen /* Update led */ 5359116e2f9SJoseph Chen leds_update(dev, soc); 5361b3009deSJoseph Chen 537f23c35a8SJoseph Chen printf("Extrem low power, force charging... threshold=%dmv, now=%dmv\n", 538f23c35a8SJoseph Chen pdata->low_power_voltage, voltage); 539f23c35a8SJoseph Chen 540f23c35a8SJoseph Chen /* System suspend */ 5412e68f6b5SJoseph Chen system_suspend_enter(dev); 542f23c35a8SJoseph Chen 543f23c35a8SJoseph Chen /* Update voltage */ 544f23c35a8SJoseph Chen voltage = fuel_gauge_get_voltage(fg); 545f23c35a8SJoseph Chen if (voltage < 0) { 546f23c35a8SJoseph Chen printf("get voltage failed: %d\n", voltage); 547f23c35a8SJoseph Chen continue; 548f23c35a8SJoseph Chen } 5495fc2a70cSJoseph Chen 5505fc2a70cSJoseph Chen if (ctrlc()) { 5515fc2a70cSJoseph Chen printf("Extrem low charge: exit by ctrl+c\n"); 5525fc2a70cSJoseph Chen break; 5535fc2a70cSJoseph Chen } 554f23c35a8SJoseph Chen } 555f23c35a8SJoseph Chen 55679244e4cSJoseph Chen autowakeup_timer_uninit(); 55779244e4cSJoseph Chen 558f23c35a8SJoseph Chen return 0; 559f23c35a8SJoseph Chen } 560f23c35a8SJoseph Chen 561f1c8ecceSJoseph Chen static int charge_animation_show(struct udevice *dev) 562f1c8ecceSJoseph Chen { 563f1c8ecceSJoseph Chen struct charge_animation_pdata *pdata = dev_get_platdata(dev); 564f1c8ecceSJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 565f1c8ecceSJoseph Chen const struct charge_image *image = priv->image; 566f1c8ecceSJoseph Chen struct udevice *fg = priv->fg; 567a8b9d026SJoseph Chen const char *preboot = env_get("preboot"); 568f1c8ecceSJoseph Chen int image_num = priv->image_num; 569f1c8ecceSJoseph Chen bool ever_lowpower_screen_off = false; 570f1c8ecceSJoseph Chen bool screen_on = true; 571f1c8ecceSJoseph Chen ulong show_start = 0, charge_start = 0, debug_start = 0; 5725e804741SJoseph Chen ulong delta; 573f1c8ecceSJoseph Chen ulong ms = 0, sec = 0; 5749116e2f9SJoseph Chen int start_idx = 0, show_idx = -1, old_show_idx = IMAGE_RECALC_IDX; 575d6653c12SJoseph Chen int soc, voltage, current, key_state; 576f23c35a8SJoseph Chen int i, charging = 1, ret; 5778f9ff705SJoseph Chen int boot_mode; 5785e804741SJoseph Chen int first_poll_fg = 1; 5799116e2f9SJoseph Chen bool lp_shutdown = false; 580f1c8ecceSJoseph Chen 581f23c35a8SJoseph Chen /* 582f23c35a8SJoseph Chen * Check sequence: 583f23c35a8SJoseph Chen * 584f23c35a8SJoseph Chen * 1. Extrem low power charge? 585f23c35a8SJoseph Chen * 2. Preboot cmd? 586f23c35a8SJoseph Chen * 3. Valid boot mode? 587f23c35a8SJoseph Chen * 4. U-Boot charge enabled by dts config? 588f23c35a8SJoseph Chen * 5. Screen off before charge? 589f23c35a8SJoseph Chen * 6. Enter charge ! 590f23c35a8SJoseph Chen * 591f23c35a8SJoseph Chen */ 59261a7a6d6SJoseph Chen if (!fuel_gauge_bat_is_exist(fg)) { 59361a7a6d6SJoseph Chen printf("Exit charge: battery is not exist\n"); 59461a7a6d6SJoseph Chen return 0; 59561a7a6d6SJoseph Chen } 59661a7a6d6SJoseph Chen 597f23c35a8SJoseph Chen /* Extrem low power charge */ 598f23c35a8SJoseph Chen ret = charge_extrem_low_power(dev); 599f23c35a8SJoseph Chen if (ret < 0) { 600f23c35a8SJoseph Chen printf("extrem low power charge failed, ret=%d\n", ret); 601f23c35a8SJoseph Chen return ret; 602f23c35a8SJoseph Chen } 603f23c35a8SJoseph Chen 604a8b9d026SJoseph Chen /* If there is preboot command, exit */ 60561a7a6d6SJoseph Chen if (preboot && !strstr(preboot, "dvfs")) { 6067ae45834SJoseph Chen printf("Exit charge: due to preboot cmd '%s'\n", preboot); 607a8b9d026SJoseph Chen return 0; 608a8b9d026SJoseph Chen } 609a8b9d026SJoseph Chen 610f23c35a8SJoseph Chen /* Not valid charge mode, exit */ 611d3ff9cf9SKever Yang #ifdef CONFIG_RKIMG_BOOTLOADER 6128f9ff705SJoseph Chen boot_mode = rockchip_get_boot_mode(); 613221b5220SJoseph Chen if ((boot_mode != BOOT_MODE_CHARGING) && 614221b5220SJoseph Chen (boot_mode != BOOT_MODE_UNDEFINE)) { 6157ae45834SJoseph Chen printf("Exit charge: due to boot mode\n"); 6168f9ff705SJoseph Chen return 0; 6178f9ff705SJoseph Chen } 6188f9ff705SJoseph Chen #endif 6199116e2f9SJoseph Chen /* No charger online + low power? shutdown */ 620038c1ecaSShunqing Chen charging = fg_charger_get_chrg_online(dev); 621038c1ecaSShunqing Chen if (charging <= 0 && pdata->auto_exit_charge) { 622038c1ecaSShunqing Chen soc = fuel_gauge_update_get_soc(fg); 623038c1ecaSShunqing Chen voltage = fuel_gauge_get_voltage(fg); 624038c1ecaSShunqing Chen if (soc < pdata->exit_charge_level) { 625038c1ecaSShunqing Chen printf("soc(%d%%) < exit_charge_level(%d%%)\n", 626038c1ecaSShunqing Chen soc, pdata->exit_charge_level); 6279116e2f9SJoseph Chen lp_shutdown = true; 628038c1ecaSShunqing Chen } 629038c1ecaSShunqing Chen if (voltage < pdata->exit_charge_voltage) { 630038c1ecaSShunqing Chen printf("voltage(%d) < exit_charge_voltage(%d)\n", 631038c1ecaSShunqing Chen voltage, pdata->exit_charge_voltage); 6329116e2f9SJoseph Chen lp_shutdown = true; 633038c1ecaSShunqing Chen } 6349116e2f9SJoseph Chen if (lp_shutdown) { 635038c1ecaSShunqing Chen printf("Not charging and low power, Shutdown...\n"); 636038c1ecaSShunqing Chen show_idx = IMAGE_LOWPOWER_IDX(image_num); 637038c1ecaSShunqing Chen charge_show_bmp(image[show_idx].name); 638ceb76aadSJoseph Chen 639ceb76aadSJoseph Chen sys_shutdown(dev); 640038c1ecaSShunqing Chen } 641038c1ecaSShunqing Chen } 642221b5220SJoseph Chen 6439116e2f9SJoseph Chen /* No charger online, exit */ 644cf49f6adSJoseph Chen if (charging <= 0) { 6457ae45834SJoseph Chen printf("Exit charge: due to charger offline\n"); 646616056c9Sshengfei Xu return 0; 647cf49f6adSJoseph Chen } 648616056c9Sshengfei Xu 649f23c35a8SJoseph Chen /* Enter android charge, set property for kernel */ 650d6653c12SJoseph Chen if (pdata->android_charge) { 651d6653c12SJoseph Chen env_update("bootargs", "androidboot.mode=charger"); 652d6653c12SJoseph Chen printf("Android charge mode\n"); 653d6653c12SJoseph Chen } 654d6653c12SJoseph Chen 655f23c35a8SJoseph Chen /* Not enable U-Boot charge, exit */ 656cf49f6adSJoseph Chen if (!pdata->uboot_charge) { 6577ae45834SJoseph Chen printf("Exit charge: due to not enable uboot charge\n"); 658d6653c12SJoseph Chen return 0; 659cf49f6adSJoseph Chen } 660d6653c12SJoseph Chen 661f1c8ecceSJoseph Chen voltage = fuel_gauge_get_voltage(fg); 662f1c8ecceSJoseph Chen if (voltage < 0) { 663f1c8ecceSJoseph Chen printf("get voltage failed: %d\n", voltage); 664f1c8ecceSJoseph Chen return -EINVAL; 665f1c8ecceSJoseph Chen } 666f1c8ecceSJoseph Chen 667f1c8ecceSJoseph Chen /* If low power, turn off screen */ 668d6653c12SJoseph Chen if (voltage <= pdata->screen_on_voltage + 50) { 669f1c8ecceSJoseph Chen screen_on = false; 670f1c8ecceSJoseph Chen ever_lowpower_screen_off = true; 671f23c35a8SJoseph Chen charge_show_bmp(NULL); 672f1c8ecceSJoseph Chen } 673f1c8ecceSJoseph Chen 674e7f9facbSJoseph Chen /* Auto wakeup */ 675e7f9facbSJoseph Chen if (pdata->auto_wakeup_interval) { 676e7f9facbSJoseph Chen printf("Auto wakeup: %dS\n", pdata->auto_wakeup_interval); 67779244e4cSJoseph Chen autowakeup_timer_init(dev, pdata->auto_wakeup_interval); 678e7f9facbSJoseph Chen } 679e7f9facbSJoseph Chen 680175257e4SJoseph Chen /* Give a message warning when CONFIG_IRQ is not enabled */ 681175257e4SJoseph Chen #ifdef CONFIG_IRQ 682d6653c12SJoseph Chen printf("Enter U-Boot charging mode\n"); 683175257e4SJoseph Chen #else 6849116e2f9SJoseph Chen printf("Enter U-Boot charging mode(IRQ)\n"); 685175257e4SJoseph Chen #endif 686f1c8ecceSJoseph Chen 687d6653c12SJoseph Chen charge_start = get_timer(0); 6885e804741SJoseph Chen delta = get_timer(0); 689e7f9facbSJoseph Chen 690f1c8ecceSJoseph Chen /* Charging ! */ 691f1c8ecceSJoseph Chen while (1) { 6925e804741SJoseph Chen /* 6935e804741SJoseph Chen * At the most time, fuel gauge is usually a i2c device, we 6945e804741SJoseph Chen * should avoid read/write all the time. We had better set 6955e804741SJoseph Chen * poll seconds to update fuel gauge info. 6965e804741SJoseph Chen */ 6975e804741SJoseph Chen if (!first_poll_fg && get_timer(delta) < FUEL_GAUGE_POLL_MS) 6985e804741SJoseph Chen goto show_images; 6995e804741SJoseph Chen 7005e804741SJoseph Chen delta = get_timer(0); 7015e804741SJoseph Chen 702f1c8ecceSJoseph Chen debug("step1 (%d)... \n", screen_on); 703f1c8ecceSJoseph Chen 704fd62311eSJoseph Chen /* 705fd62311eSJoseph Chen * Most fuel gauge is I2C interface, it shouldn't be interrupted 706ebe3d004SJoseph Chen * during transfer. The power key event depends on interrupt, so 707ebe3d004SJoseph Chen * we should disable local irq when update fuel gauge. 708fd62311eSJoseph Chen */ 709fd62311eSJoseph Chen local_irq_disable(); 710fd62311eSJoseph Chen 711f1c8ecceSJoseph Chen /* Step1: Is charging now ? */ 7124d083e3fSJoseph Chen charging = fg_charger_get_chrg_online(dev); 713f1c8ecceSJoseph Chen if (charging <= 0) { 714f1c8ecceSJoseph Chen printf("Not charging, online=%d. Shutdown...\n", 715f1c8ecceSJoseph Chen charging); 716255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY 717255e5751SWenping Zhang /* 71877bac292SZorro Liu * If charger is plug out during charging, display poweroff 71977bac292SZorro Liu * image before device power off. 720255e5751SWenping Zhang * Irq must be enable if CONFIG_IRQ is defined, because 721255e5751SWenping Zhang * ebc need to wait irq to indicate frame is complete. 722255e5751SWenping Zhang */ 723255e5751SWenping Zhang local_irq_enable(); 7249116e2f9SJoseph Chen 72577bac292SZorro Liu ret = rockchip_eink_show_charge_logo(EINK_LOGO_POWEROFF); 726255e5751SWenping Zhang if (ret != 0) 727255e5751SWenping Zhang printf("Eink display reset logo failed\n"); 7289116e2f9SJoseph Chen 729255e5751SWenping Zhang local_irq_disable(); 730255e5751SWenping Zhang #endif 731ceb76aadSJoseph Chen sys_shutdown(dev); 732f1c8ecceSJoseph Chen continue; 733f1c8ecceSJoseph Chen } 734f1c8ecceSJoseph Chen 735f1c8ecceSJoseph Chen debug("step2 (%d)... show_idx=%d\n", screen_on, show_idx); 736f1c8ecceSJoseph Chen 737f1c8ecceSJoseph Chen /* Step2: get soc and voltage */ 7384f9cab27SJoseph Chen soc = fuel_gauge_update_get_soc(fg); 739f1c8ecceSJoseph Chen if (soc < 0 || soc > 100) { 740f1c8ecceSJoseph Chen printf("get soc failed: %d\n", soc); 741f1c8ecceSJoseph Chen continue; 742f1c8ecceSJoseph Chen } 743f1c8ecceSJoseph Chen 744f1c8ecceSJoseph Chen voltage = fuel_gauge_get_voltage(fg); 745f1c8ecceSJoseph Chen if (voltage < 0) { 746f1c8ecceSJoseph Chen printf("get voltage failed: %d\n", voltage); 747f1c8ecceSJoseph Chen continue; 748f1c8ecceSJoseph Chen } 749f1c8ecceSJoseph Chen 750d6653c12SJoseph Chen current = fuel_gauge_get_current(fg); 751d6653c12SJoseph Chen if (current == -ENOSYS) { 752d6653c12SJoseph Chen printf("get current failed: %d\n", current); 753d6653c12SJoseph Chen continue; 754d6653c12SJoseph Chen } 755fd62311eSJoseph Chen 756ebe3d004SJoseph Chen first_poll_fg = 0; 757fd62311eSJoseph Chen local_irq_enable(); 758fd62311eSJoseph Chen 759038c1ecaSShunqing Chen if (pdata->auto_exit_charge) { 760038c1ecaSShunqing Chen /* Is able to boot now ? */ 761038c1ecaSShunqing Chen if (pdata->exit_charge_level && 762038c1ecaSShunqing Chen soc >= pdata->exit_charge_level) { 763038c1ecaSShunqing Chen printf("soc(%d%%) exit charge animation...\n", 764038c1ecaSShunqing Chen soc); 765038c1ecaSShunqing Chen break; 766038c1ecaSShunqing Chen } 767038c1ecaSShunqing Chen if (pdata->exit_charge_voltage && 768038c1ecaSShunqing Chen voltage >= pdata->exit_charge_voltage) { 769038c1ecaSShunqing Chen printf("vol(%d) exit charge animation...\n", 770038c1ecaSShunqing Chen voltage); 771038c1ecaSShunqing Chen break; 772038c1ecaSShunqing Chen } 773038c1ecaSShunqing Chen } 774038c1ecaSShunqing Chen 7755e804741SJoseph Chen show_images: 776f1c8ecceSJoseph Chen /* 777f1c8ecceSJoseph Chen * Just for debug, otherwise there will be nothing output which 778f1c8ecceSJoseph Chen * is not good to know what happen. 779f1c8ecceSJoseph Chen */ 780f1c8ecceSJoseph Chen if (!debug_start) 781f1c8ecceSJoseph Chen debug_start = get_timer(0); 7829116e2f9SJoseph Chen if (get_timer(debug_start) > 30000) { 783f1c8ecceSJoseph Chen debug_start = get_timer(0); 784ebe3d004SJoseph Chen printf("[%8ld]: soc=%d%%, vol=%dmv, c=%dma, " 785ebe3d004SJoseph Chen "online=%d, screen_on=%d\n", 786d6653c12SJoseph Chen get_timer(0) / 1000, soc, voltage, 787d6653c12SJoseph Chen current, charging, screen_on); 788f1c8ecceSJoseph Chen } 789f1c8ecceSJoseph Chen 7901b3009deSJoseph Chen /* Update leds */ 7919116e2f9SJoseph Chen leds_update(dev, soc); 7921b3009deSJoseph Chen 793f1c8ecceSJoseph Chen /* 794f23c35a8SJoseph Chen * If ever lowpower screen off, force screen_on=false, which 795f1c8ecceSJoseph Chen * means key event can't modify screen_on, only voltage higher 796f1c8ecceSJoseph Chen * then threshold can update screen_on=true; 797f1c8ecceSJoseph Chen */ 798f1c8ecceSJoseph Chen if (ever_lowpower_screen_off) 799f1c8ecceSJoseph Chen screen_on = false; 800f1c8ecceSJoseph Chen 801f1c8ecceSJoseph Chen /* 802f1c8ecceSJoseph Chen * Auto turn on screen when voltage higher than Vol screen on. 803ebe3d004SJoseph Chen * 'ever_lowpower_screen_off' means enter the while(1) loop with 804f1c8ecceSJoseph Chen * screen off. 805f1c8ecceSJoseph Chen */ 806f1c8ecceSJoseph Chen if ((ever_lowpower_screen_off) && 807d6653c12SJoseph Chen (voltage > pdata->screen_on_voltage)) { 808f1c8ecceSJoseph Chen ever_lowpower_screen_off = false; 809f1c8ecceSJoseph Chen screen_on = true; 8109116e2f9SJoseph Chen show_idx = IMAGE_RECALC_IDX; 811f1c8ecceSJoseph Chen } 812f1c8ecceSJoseph Chen 813f1c8ecceSJoseph Chen /* 8149116e2f9SJoseph Chen * IMAGE_RECALC_IDX means show_idx show be update by start_idx. 815f1c8ecceSJoseph Chen * When short key pressed event trigged, we will set show_idx 8169116e2f9SJoseph Chen * as IMAGE_RECALC_IDX which updates images index from start_idx 817f1c8ecceSJoseph Chen * that calculate by current soc. 818f1c8ecceSJoseph Chen */ 8199116e2f9SJoseph Chen if (show_idx == IMAGE_RECALC_IDX) { 820ebe3d004SJoseph Chen for (i = 0; i < IMAGE_SOC_100_IDX(image_num); i++) { 821f1c8ecceSJoseph Chen /* Find out which image we start to show */ 8229116e2f9SJoseph Chen if ((soc >= image[i].soc) && (soc < image[i + 1].soc)) { 823f1c8ecceSJoseph Chen start_idx = i; 824f1c8ecceSJoseph Chen break; 825f1c8ecceSJoseph Chen } 826f1c8ecceSJoseph Chen 827f1c8ecceSJoseph Chen if (soc >= 100) { 828ebe3d004SJoseph Chen start_idx = IMAGE_SOC_100_IDX(image_num); 829f1c8ecceSJoseph Chen break; 830f1c8ecceSJoseph Chen } 831f1c8ecceSJoseph Chen } 832f1c8ecceSJoseph Chen 833f1c8ecceSJoseph Chen debug("%s: show_idx=%d, screen_on=%d\n", 834f1c8ecceSJoseph Chen __func__, show_idx, screen_on); 835f1c8ecceSJoseph Chen 836f1c8ecceSJoseph Chen /* Mark start index and start time */ 837f1c8ecceSJoseph Chen show_idx = start_idx; 838f1c8ecceSJoseph Chen show_start = get_timer(0); 839f1c8ecceSJoseph Chen } 840f1c8ecceSJoseph Chen 841f1c8ecceSJoseph Chen debug("step3 (%d)... show_idx=%d\n", screen_on, show_idx); 842f1c8ecceSJoseph Chen 843255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY 844255e5751SWenping Zhang /* 845255e5751SWenping Zhang * Device is auto wakeup from suspend, if it's eink display, 846255e5751SWenping Zhang * screen will display the last image after suspend, so 847255e5751SWenping Zhang * we should update the image to show the approximate 848255e5751SWenping Zhang * battery power if battery is charging to next level. 849255e5751SWenping Zhang */ 850255e5751SWenping Zhang if (pdata->auto_wakeup_interval && 851255e5751SWenping Zhang priv->auto_wakeup_key_state == KEY_PRESS_DOWN && 852255e5751SWenping Zhang !screen_on) { 853255e5751SWenping Zhang if (soc >= image[old_show_idx + 1].soc && 854255e5751SWenping Zhang soc < 100) { 855255e5751SWenping Zhang int ret; 856255e5751SWenping Zhang int logo_type = EINK_LOGO_CHARGING_0; 857255e5751SWenping Zhang 858255e5751SWenping Zhang logo_type = logo_type << (old_show_idx + 1); 859255e5751SWenping Zhang ret = rockchip_eink_show_charge_logo(logo_type); 860255e5751SWenping Zhang /* 861255e5751SWenping Zhang * only change the logic if eink is 862255e5751SWenping Zhang * actually exist 863255e5751SWenping Zhang */ 864255e5751SWenping Zhang if (ret == 0) { 865255e5751SWenping Zhang printf("Update image id[%d] for eink\n", 866255e5751SWenping Zhang old_show_idx + 1); 867255e5751SWenping Zhang old_show_idx++; 868255e5751SWenping Zhang } 869255e5751SWenping Zhang } 870255e5751SWenping Zhang } 871255e5751SWenping Zhang /* 872255e5751SWenping Zhang * If battery capacity is charged to 100%, exit charging 873255e5751SWenping Zhang * animation and boot android system. 874255e5751SWenping Zhang */ 875255e5751SWenping Zhang if (soc >= 100) { 876255e5751SWenping Zhang int ret; 877255e5751SWenping Zhang int logo_type = EINK_LOGO_CHARGING_5; 878255e5751SWenping Zhang 879255e5751SWenping Zhang ret = rockchip_eink_show_charge_logo(logo_type); 880255e5751SWenping Zhang /* Only change the logic if eink is acutally exist */ 881255e5751SWenping Zhang if (ret == 0) { 882255e5751SWenping Zhang printf("battery FULL,exit charge animation\n"); 883255e5751SWenping Zhang mdelay(20); 884255e5751SWenping Zhang break; 885255e5751SWenping Zhang } 886255e5751SWenping Zhang } 887255e5751SWenping Zhang #endif 888f1c8ecceSJoseph Chen /* Step3: show images */ 889f1c8ecceSJoseph Chen if (screen_on) { 89093aee2d9SJoseph Chen /* Don't call 'charge_show_bmp' unless image changed */ 89193aee2d9SJoseph Chen if (old_show_idx != show_idx) { 892255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY 893255e5751SWenping Zhang int logo_type = EINK_LOGO_CHARGING_0; 894255e5751SWenping Zhang 895255e5751SWenping Zhang rockchip_eink_show_charge_logo(logo_type << 896255e5751SWenping Zhang show_idx); 897255e5751SWenping Zhang #endif 89893aee2d9SJoseph Chen old_show_idx = show_idx; 899f1c8ecceSJoseph Chen debug("SHOW: %s\n", image[show_idx].name); 900f23c35a8SJoseph Chen charge_show_bmp(image[show_idx].name); 90193aee2d9SJoseph Chen } 902ebe3d004SJoseph Chen /* Re-calculate timeout to off screen */ 903e7f9facbSJoseph Chen if (priv->auto_screen_off_timeout == 0) 904e7f9facbSJoseph Chen priv->auto_screen_off_timeout = get_timer(0); 905f1c8ecceSJoseph Chen } else { 9069116e2f9SJoseph Chen /* Entering low power suspend mode !!! */ 907e7f9facbSJoseph Chen priv->auto_screen_off_timeout = 0; 9082e68f6b5SJoseph Chen system_suspend_enter(dev); 909f1c8ecceSJoseph Chen } 910f1c8ecceSJoseph Chen 911f1c8ecceSJoseph Chen mdelay(5); 912f1c8ecceSJoseph Chen 9139116e2f9SJoseph Chen /* It's time to show next image ? */ 914f1c8ecceSJoseph Chen if (get_timer(show_start) > image[show_idx].period) { 915f1c8ecceSJoseph Chen show_start = get_timer(0); 916f1c8ecceSJoseph Chen show_idx++; 917ebe3d004SJoseph Chen if (show_idx > IMAGE_SOC_100_IDX(image_num)) 9189116e2f9SJoseph Chen show_idx = IMAGE_RECALC_IDX; 919f1c8ecceSJoseph Chen } 920f1c8ecceSJoseph Chen 921f1c8ecceSJoseph Chen debug("step4 (%d)... \n", screen_on); 922f1c8ecceSJoseph Chen 923f1c8ecceSJoseph Chen /* 924f1c8ecceSJoseph Chen * Step4: check key event. 925f1c8ecceSJoseph Chen * 926f1c8ecceSJoseph Chen * Short key event: turn on/off screen; 927f1c8ecceSJoseph Chen * Long key event: show logo and boot system or still charging. 928f1c8ecceSJoseph Chen */ 929e7f9facbSJoseph Chen key_state = check_key_press(dev); 930b177a917SJoseph Chen if (key_state == KEY_PRESS_DOWN) { 9319116e2f9SJoseph Chen /* Clear current image index, recalc image index */ 9329116e2f9SJoseph Chen old_show_idx = IMAGE_RECALC_IDX; 9339116e2f9SJoseph Chen show_idx = IMAGE_RECALC_IDX; 934f1c8ecceSJoseph Chen 935f1c8ecceSJoseph Chen /* 936ebe3d004SJoseph Chen * Reverse the screen state 937f1c8ecceSJoseph Chen * 938f1c8ecceSJoseph Chen * If screen_on=false, means this short key pressed 939f1c8ecceSJoseph Chen * event turn on the screen and we need show images. 940f1c8ecceSJoseph Chen * 941f1c8ecceSJoseph Chen * If screen_on=true, means this short key pressed 942f1c8ecceSJoseph Chen * event turn off the screen and we never show images. 943f1c8ecceSJoseph Chen */ 944ebe3d004SJoseph Chen if (screen_on) { 945255e5751SWenping Zhang #ifdef CONFIG_ROCKCHIP_EINK_DISPLAY 946255e5751SWenping Zhang int type = EINK_LOGO_CHARGING_0 << start_idx; 947255e5751SWenping Zhang /* 948255e5751SWenping Zhang * Show current battery capacity before suspend 949255e5751SWenping Zhang * if it's eink display, because eink screen 950255e5751SWenping Zhang * will continue to display the last image 951255e5751SWenping Zhang * after suspend, so user can get the 952255e5751SWenping Zhang * approximate capacity by image displayed. 953255e5751SWenping Zhang */ 954255e5751SWenping Zhang ret = rockchip_eink_show_charge_logo(type); 955255e5751SWenping Zhang /* only change the logic if eink display ok */ 956255e5751SWenping Zhang if (ret == 0) 957255e5751SWenping Zhang old_show_idx = start_idx; 958255e5751SWenping Zhang #endif 959ebe3d004SJoseph Chen charge_show_bmp(NULL); /* Turn off screen */ 960f1c8ecceSJoseph Chen screen_on = false; 9612e68f6b5SJoseph Chen priv->suspend_delay_timeout = get_timer(0); 962ebe3d004SJoseph Chen } else { 963f1c8ecceSJoseph Chen screen_on = true; 964ebe3d004SJoseph Chen } 9652e68f6b5SJoseph Chen 9662e68f6b5SJoseph Chen printf("screen %s\n", screen_on ? "on" : "off"); 967b177a917SJoseph Chen } else if (key_state == KEY_PRESS_LONG_DOWN) { 968ebe3d004SJoseph Chen /* Set screen_on=true anyway when key long pressed */ 969f1c8ecceSJoseph Chen if (!screen_on) 970f1c8ecceSJoseph Chen screen_on = true; 971f1c8ecceSJoseph Chen 9722e68f6b5SJoseph Chen printf("screen %s\n", screen_on ? "on" : "off"); 9732e68f6b5SJoseph Chen 974f1c8ecceSJoseph Chen /* Is able to boot now ? */ 975d6653c12SJoseph Chen if (soc < pdata->exit_charge_level) { 976f1c8ecceSJoseph Chen printf("soc=%d%%, threshold soc=%d%%\n", 977d6653c12SJoseph Chen soc, pdata->exit_charge_level); 978f1c8ecceSJoseph Chen printf("Low power, unable to boot, charging...\n"); 979ebe3d004SJoseph Chen show_idx = IMAGE_LOWPOWER_IDX(image_num); 980f1c8ecceSJoseph Chen continue; 981f1c8ecceSJoseph Chen } 982f1c8ecceSJoseph Chen 983d6653c12SJoseph Chen if (voltage < pdata->exit_charge_voltage) { 984f1c8ecceSJoseph Chen printf("voltage=%dmv, threshold voltage=%dmv\n", 985d6653c12SJoseph Chen voltage, pdata->exit_charge_voltage); 986f1c8ecceSJoseph Chen printf("Low power, unable to boot, charging...\n"); 987ebe3d004SJoseph Chen show_idx = IMAGE_LOWPOWER_IDX(image_num); 988f1c8ecceSJoseph Chen continue; 989f1c8ecceSJoseph Chen } 990f1c8ecceSJoseph Chen 991f1c8ecceSJoseph Chen /* Success exit charging */ 992f1c8ecceSJoseph Chen printf("Exit charge animation...\n"); 993f23c35a8SJoseph Chen charge_show_logo(); 994f1c8ecceSJoseph Chen break; 995f1c8ecceSJoseph Chen } else { 996f1c8ecceSJoseph Chen /* Do nothing */ 997f1c8ecceSJoseph Chen } 998f1c8ecceSJoseph Chen 999f1c8ecceSJoseph Chen debug("step5 (%d)... \n", screen_on); 1000f1c8ecceSJoseph Chen 1001d6653c12SJoseph Chen /* Step5: Exit by ctrl+c */ 10021367bfe3SJoseph Chen if (ctrlc()) { 1003d6653c12SJoseph Chen if (voltage >= pdata->screen_on_voltage) 1004f23c35a8SJoseph Chen charge_show_logo(); 10051367bfe3SJoseph Chen printf("Exit charge, due to ctrl+c\n"); 10061367bfe3SJoseph Chen break; 10071367bfe3SJoseph Chen } 1008f1c8ecceSJoseph Chen } 1009f1c8ecceSJoseph Chen 1010e7f9facbSJoseph Chen if (pdata->auto_wakeup_interval) 1011e7f9facbSJoseph Chen autowakeup_timer_uninit(); 1012e7f9facbSJoseph Chen 1013f1c8ecceSJoseph Chen ms = get_timer(charge_start); 1014f1c8ecceSJoseph Chen if (ms >= 1000) { 1015f1c8ecceSJoseph Chen sec = ms / 1000; 1016f1c8ecceSJoseph Chen ms = ms % 1000; 1017f1c8ecceSJoseph Chen } 1018f1c8ecceSJoseph Chen 1019f1c8ecceSJoseph Chen printf("charging time total: %lu.%lus, soc=%d%%, vol=%dmv\n", 1020f1c8ecceSJoseph Chen sec, ms, soc, voltage); 1021f1c8ecceSJoseph Chen 1022f1c8ecceSJoseph Chen return 0; 1023f1c8ecceSJoseph Chen } 1024f1c8ecceSJoseph Chen 10254d083e3fSJoseph Chen static int fg_charger_get_device(struct udevice **fuel_gauge, 10264d083e3fSJoseph Chen struct udevice **charger) 10274d083e3fSJoseph Chen { 10284d083e3fSJoseph Chen struct udevice *dev; 10294d083e3fSJoseph Chen struct uclass *uc; 10304d083e3fSJoseph Chen int ret, cap; 10314d083e3fSJoseph Chen 10324d083e3fSJoseph Chen *fuel_gauge = NULL, 10334d083e3fSJoseph Chen *charger = NULL; 10344d083e3fSJoseph Chen 10354d083e3fSJoseph Chen ret = uclass_get(UCLASS_FG, &uc); 10364d083e3fSJoseph Chen if (ret) 10374d083e3fSJoseph Chen return ret; 10384d083e3fSJoseph Chen 10394d083e3fSJoseph Chen for (uclass_first_device(UCLASS_FG, &dev); 10404d083e3fSJoseph Chen dev; 10414d083e3fSJoseph Chen uclass_next_device(&dev)) { 10424d083e3fSJoseph Chen cap = fuel_gauge_capability(dev); 10434d083e3fSJoseph Chen if (cap == (FG_CAP_CHARGER | FG_CAP_FUEL_GAUGE)) { 10444d083e3fSJoseph Chen *fuel_gauge = dev; 10454d083e3fSJoseph Chen *charger = NULL; 10464d083e3fSJoseph Chen } else if (cap == FG_CAP_FUEL_GAUGE) { 10474d083e3fSJoseph Chen *fuel_gauge = dev; 10484d083e3fSJoseph Chen } else if (cap == FG_CAP_CHARGER) { 10494d083e3fSJoseph Chen *charger = dev; 10504d083e3fSJoseph Chen } 10514d083e3fSJoseph Chen } 10524d083e3fSJoseph Chen 10534d083e3fSJoseph Chen return (*fuel_gauge) ? 0 : -ENODEV; 10544d083e3fSJoseph Chen } 10554d083e3fSJoseph Chen 1056f1c8ecceSJoseph Chen static const struct dm_charge_display_ops charge_animation_ops = { 1057f1c8ecceSJoseph Chen .show = charge_animation_show, 1058f1c8ecceSJoseph Chen }; 1059f1c8ecceSJoseph Chen 1060f1c8ecceSJoseph Chen static int charge_animation_probe(struct udevice *dev) 1061f1c8ecceSJoseph Chen { 1062f1c8ecceSJoseph Chen struct charge_animation_priv *priv = dev_get_priv(dev); 1063f23c35a8SJoseph Chen int ret, soc; 1064f1c8ecceSJoseph Chen 1065f23c35a8SJoseph Chen /* Get PMIC: used for power off system */ 1066ebe3d004SJoseph Chen ret = uclass_get_device(UCLASS_PMIC, 0, &priv->pmic); 1067f1c8ecceSJoseph Chen if (ret) { 1068cf49f6adSJoseph Chen if (ret == -ENODEV) 1069cf49f6adSJoseph Chen printf("Can't find PMIC\n"); 1070cf49f6adSJoseph Chen else 1071f1c8ecceSJoseph Chen printf("Get UCLASS PMIC failed: %d\n", ret); 1072f1c8ecceSJoseph Chen return ret; 1073f1c8ecceSJoseph Chen } 1074f1c8ecceSJoseph Chen 10754d083e3fSJoseph Chen /* Get fuel gauge and charger(If need) */ 10764d083e3fSJoseph Chen ret = fg_charger_get_device(&priv->fg, &priv->charger); 1077f1c8ecceSJoseph Chen if (ret) { 1078cf49f6adSJoseph Chen if (ret == -ENODEV) 107931ab5b3bSJoseph Chen debug("Can't find FG\n"); 1080cf49f6adSJoseph Chen else 108131ab5b3bSJoseph Chen debug("Get UCLASS FG failed: %d\n", ret); 1082f1c8ecceSJoseph Chen return ret; 1083f1c8ecceSJoseph Chen } 1084f1c8ecceSJoseph Chen 10858b436ce5SElaine Zhang /* Get rtc: used for power on */ 1086ebe3d004SJoseph Chen ret = uclass_get_device(UCLASS_RTC, 0, &priv->rtc); 10878b436ce5SElaine Zhang if (ret) { 10888b436ce5SElaine Zhang if (ret == -ENODEV) 10898b436ce5SElaine Zhang debug("Can't find RTC\n"); 10908b436ce5SElaine Zhang else 10918b436ce5SElaine Zhang debug("Get UCLASS RTC failed: %d\n", ret); 10928b436ce5SElaine Zhang } 10938b436ce5SElaine Zhang 109479244e4cSJoseph Chen /* Get PWRKEY: used for wakeup and turn off/on LCD */ 1095d0e4711bSJoseph Chen if (!key_exist(KEY_POWER)) { 109631ab5b3bSJoseph Chen debug("Can't find power key\n"); 1097f23c35a8SJoseph Chen return -EINVAL; 1098f23c35a8SJoseph Chen } 1099f23c35a8SJoseph Chen 1100f23c35a8SJoseph Chen /* Initialize charge current */ 11014f9cab27SJoseph Chen soc = fuel_gauge_update_get_soc(priv->fg); 1102f23c35a8SJoseph Chen if (soc < 0 || soc > 100) { 110331ab5b3bSJoseph Chen debug("get soc failed: %d\n", soc); 1104f23c35a8SJoseph Chen return -EINVAL; 1105f23c35a8SJoseph Chen } 1106f23c35a8SJoseph Chen 11071b3009deSJoseph Chen /* Get leds */ 11081b3009deSJoseph Chen #ifdef CONFIG_LED 11091b3009deSJoseph Chen ret = led_get_by_label(LED_CHARGING_NAME, &priv->led_charging); 11101b3009deSJoseph Chen if (!ret) 11111b3009deSJoseph Chen printf("Found Charging LED\n"); 11121b3009deSJoseph Chen ret = led_get_by_label(LED_CHARGING_FULL_NAME, &priv->led_full); 11131b3009deSJoseph Chen if (!ret) 11141b3009deSJoseph Chen printf("Found Charging-Full LED\n"); 11151b3009deSJoseph Chen #endif 11161b3009deSJoseph Chen 1117f23c35a8SJoseph Chen /* Get charge images */ 1118f1c8ecceSJoseph Chen priv->image = image; 1119f1c8ecceSJoseph Chen priv->image_num = ARRAY_SIZE(image); 1120f1c8ecceSJoseph Chen 1121f1c8ecceSJoseph Chen printf("Enable charge animation display\n"); 1122f1c8ecceSJoseph Chen 1123f1c8ecceSJoseph Chen return 0; 1124f1c8ecceSJoseph Chen } 1125f1c8ecceSJoseph Chen 1126f1c8ecceSJoseph Chen static const struct udevice_id charge_animation_ids[] = { 1127d6653c12SJoseph Chen { .compatible = "rockchip,uboot-charge" }, 1128f1c8ecceSJoseph Chen { }, 1129f1c8ecceSJoseph Chen }; 1130f1c8ecceSJoseph Chen 1131f1c8ecceSJoseph Chen U_BOOT_DRIVER(charge_animation) = { 1132f1c8ecceSJoseph Chen .name = "charge-animation", 1133f1c8ecceSJoseph Chen .id = UCLASS_CHARGE_DISPLAY, 1134f1c8ecceSJoseph Chen .probe = charge_animation_probe, 1135f1c8ecceSJoseph Chen .of_match = charge_animation_ids, 1136f1c8ecceSJoseph Chen .ops = &charge_animation_ops, 1137f1c8ecceSJoseph Chen .ofdata_to_platdata = charge_animation_ofdata_to_platdata, 1138f1c8ecceSJoseph Chen .platdata_auto_alloc_size = sizeof(struct charge_animation_pdata), 1139f1c8ecceSJoseph Chen .priv_auto_alloc_size = sizeof(struct charge_animation_priv), 1140f1c8ecceSJoseph Chen }; 1141