/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * TI TPS65090 PMU charging task. */ #include "battery.h" #include "battery_smart.h" #include "clock.h" #include "chipset.h" #include "common.h" #include "console.h" #include "extpower.h" #include "hooks.h" #include "gpio.h" #include "pmu_tpschrome.h" #include "system.h" #include "task.h" #include "timer.h" #include "util.h" #define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args) /* Charging and discharging alarms */ #define ALARM_DISCHARGING (ALARM_TERMINATE_DISCHARGE | ALARM_OVER_TEMP) #define ALARM_CHARGED (ALARM_OVER_CHARGED | ALARM_TERMINATE_CHARGE) /* Maximum time allowed to revive a extremely low charge battery */ #define PRE_CHARGING_TIMEOUT (15 * SECOND) /* * Time delay in usec for idle, charging and discharging. Defined in battery * charging flow. */ #define T1_OFF_USEC (60 * SECOND) #define T1_SUSPEND_USEC (60 * SECOND) #define T1_USEC (5 * SECOND) #define T2_USEC (10 * SECOND) #define T3_USEC (10 * SECOND) #ifndef BATTERY_AP_OFF_LEVEL #define BATTERY_AP_OFF_LEVEL 0 #endif static const char * const state_list[] = CHARGE_STATE_NAME_TABLE; /* States for throttling PMU task */ static timestamp_t last_waken; /* Initialized to 0 */ static int has_pending_event; static enum charging_state current_state = ST_IDLE0; /* Cached version of battery parameter */ static struct batt_params batt_params_copy; #ifdef CONFIG_PMU_TPS65090_CHARGING_LED static void update_battery_led(void) { int alarm; int led_on = 0; if(extpower_is_present()){ battery_status(&alarm); if((alarm & ALARM_CHARGED) && !gpio_get_level(GPIO_CHARGER_EN)) led_on = 1; } gpio_set_level(GPIO_CHARGING_LED, led_on); } #endif static void enable_charging(int enable) { enable = enable ? 1 : 0; if (gpio_get_level(GPIO_CHARGER_EN) != enable) gpio_set_level(GPIO_CHARGER_EN, enable); } static int battery_start_charging_range(int deci_k) { int8_t temp_c = DECI_KELVIN_TO_CELSIUS(deci_k); const struct battery_info *info = battery_get_info(); return (temp_c >= info->start_charging_min_c && temp_c < info->start_charging_max_c); } static int battery_charging_range(int deci_k) { int8_t temp_c = DECI_KELVIN_TO_CELSIUS(deci_k); const struct battery_info *info = battery_get_info(); return (temp_c >= info->charging_min_c && temp_c < info->charging_max_c); } static int battery_discharging_range(int deci_k) { int8_t temp_c = DECI_KELVIN_TO_CELSIUS(deci_k); const struct battery_info *info = battery_get_info(); return (temp_c >= info->discharging_min_c && temp_c < info->discharging_max_c); } /** * Turn off the AP */ static int system_off(void) { if (chipset_in_state(CHIPSET_STATE_ON)) { CPRINTS("pmu turning system off"); chipset_force_shutdown(); } return ST_IDLE0; } /** * Notify the host when battery remaining charge is lower than 10% */ static int notify_battery_low(void) { static timestamp_t last_notify_time; timestamp_t now; if (chipset_in_state(CHIPSET_STATE_ON)) { now = get_time(); if (now.val - last_notify_time.val > MINUTE) { CPRINTS("pmu notify battery low (< 4%)"); last_notify_time = now; /* TODO(crosbug.com/p/23814): Actually notify AP */ } } return ST_DISCHARGING; } /* * Calculate relative state of charge moving average * * @param state_of_charge Current battery state of charge reading, * from 0 to 100. When state_of_charge < 0, * resets the moving average window * @return Average state of charge, rounded to nearest * integer. * -1 when window reset. * * The returned value will be rounded to the nearest integer, by set moving * average init value to (0.5 * window_size). * */ static int rsoc_moving_average(int state_of_charge) { static uint8_t rsoc[4]; static int8_t index = -1; int moving_average = ARRAY_SIZE(rsoc) / 2; int i; if (state_of_charge < 0) { index = -1; return -1; } if (index < 0) { for (i = 0; i < ARRAY_SIZE(rsoc); i++) rsoc[i] = (uint8_t)state_of_charge; index = 0; return state_of_charge; } rsoc[index] = (uint8_t)state_of_charge; index++; index %= sizeof(rsoc); for (i = 0; i < ARRAY_SIZE(rsoc); i++) moving_average += (int)rsoc[i]; moving_average /= ARRAY_SIZE(rsoc); return moving_average; } /* * This saves battery parameters for charger_current_battery_params(). */ static void battery_get_params_and_save_a_copy(struct batt_params *batt) { battery_get_params(&batt_params_copy); memcpy(batt, &batt_params_copy, sizeof(*batt)); } struct batt_params *charger_current_battery_params(void) { return &batt_params_copy; } static int calc_next_state(int state) { struct batt_params batt; int alarm; battery_get_params_and_save_a_copy(&batt); switch (state) { case ST_IDLE0: case ST_BAD_COND: case ST_IDLE: /* Check AC and chipset state */ if (!extpower_is_present()) { if (chipset_in_state(CHIPSET_STATE_ON)) return ST_DISCHARGING; return ST_IDLE; } /* Stay in idle mode if charger overtemp */ if (pmu_is_charger_alarm()) return ST_BAD_COND; /* Enable charging when battery doesn't respond */ if (!(batt.flags & BATT_FLAG_RESPONSIVE)) return ST_PRE_CHARGING; /* Turn off charger when battery temperature is out * of the start charging range. */ if (!battery_start_charging_range(batt.temperature)) return ST_BAD_COND; /* Turn off charger on battery over temperature alarm */ if (battery_status(&alarm) || (alarm & ALARM_OVER_TEMP)) return ST_BAD_COND; /* Stop charging if the battery says it's charged */ if (alarm & ALARM_CHARGED) return ST_IDLE; /* Start charging only when battery charge lower than 100% */ if (!(batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE)) { if (batt.state_of_charge < 100) return ST_CHARGING; } return ST_IDLE; case ST_PRE_CHARGING: if (!extpower_is_present()) return ST_IDLE0; /* * If the battery goes online after enabling the charger, go * into charging state. */ if (batt.flags & BATT_FLAG_RESPONSIVE) { if (!battery_start_charging_range(batt.temperature)) return ST_IDLE0; if (!(batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE)) { if (batt.state_of_charge >= 100) return ST_IDLE0; } return ST_CHARGING; } return ST_PRE_CHARGING; case ST_CHARGING: /* Go back to idle state when AC is unplugged */ if (!extpower_is_present()) return ST_IDLE0; /* * Disable charging on battery access error, or charging * temperature out of range. */ if (!(batt.flags & BATT_FLAG_RESPONSIVE)) { CPRINTS("pmu charging: unable to get battery " "temperature"); return ST_IDLE0; } else if (!battery_charging_range(batt.temperature)) { CPRINTS("pmu charging: temperature out of range " "%dC", DECI_KELVIN_TO_CELSIUS(batt.temperature)); return ST_CHARGING_ERROR; } /* * Disable charging on battery alarm events or access error: * - over temperature * - over current */ if (battery_status(&alarm)) return ST_IDLE0; if (alarm & ALARM_OVER_TEMP) { CPRINTS("pmu charging: battery over temp"); return ST_CHARGING_ERROR; } /* Go to idle state if battery is fully charged */ if (alarm & ALARM_CHARGED) return ST_IDLE; /* * Disable charging on charger alarm events: * - charger over current * - charger over temperature */ if (pmu_is_charger_alarm()) { CPRINTS("pmu charging: charger alarm"); return ST_IDLE0; } return ST_CHARGING; case ST_CHARGING_ERROR: /* * This state indicates AC is plugged but the battery is not * charging. The conditions to exit this state: * - battery detected * - battery temperature is in start charging range * - no battery alarm */ if (extpower_is_present()) { if (battery_status(&alarm)) return ST_CHARGING_ERROR; if (alarm & ALARM_OVER_TEMP) return ST_CHARGING_ERROR; if (!(batt.flags & BATT_FLAG_RESPONSIVE)) return ST_CHARGING_ERROR; if (!battery_charging_range(batt.temperature)) return ST_CHARGING_ERROR; return ST_CHARGING; } return ST_IDLE0; case ST_DISCHARGING: /* Go back to idle state when AC is plugged */ if (extpower_is_present()) return ST_IDLE0; /* Prepare EC sleep after system stopped discharging */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) return ST_IDLE0; /* Check battery discharging temperature range */ if (batt.flags & BATT_FLAG_RESPONSIVE) { if (!battery_discharging_range(batt.temperature)) { CPRINTS("pmu discharging: temperature out of " "range %dC", DECI_KELVIN_TO_CELSIUS( batt.temperature)); return system_off(); } } /* Check discharging alarm */ if (!battery_status(&alarm) && (alarm & ALARM_DISCHARGING)) { CPRINTS("pmu discharging: battery alarm %016b", alarm); return system_off(); } /* Check remaining charge % */ if (!(batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE)) { /* * Shutdown AP when state of charge < 1.5%. * Moving average is rounded to integer. */ if (rsoc_moving_average(batt.state_of_charge) < 2) return system_off(); else if (batt.state_of_charge < 4) notify_battery_low(); } return ST_DISCHARGING; } return ST_IDLE0; } enum charging_state charge_get_state(void) { return current_state; } int charge_keep_power_off(void) { int charge; if (BATTERY_AP_OFF_LEVEL == 0) return 0; if (battery_remaining_capacity(&charge)) return current_state != ST_CHARGING_ERROR; return charge <= BATTERY_AP_OFF_LEVEL; } void charger_task(void) { int next_state; int wait_time = T1_USEC; timestamp_t pre_chg_start = get_time(); pmu_init(); /* Enable low current charging */ pmu_low_current_charging(1); /* Enable charger interrupt */ gpio_enable_interrupt(GPIO_CHARGER_INT_L); /* * EC STOP mode support * The charging loop can be stopped in idle state with AC unplugged. * Charging loop will be resumed by TPSCHROME interrupt. */ enable_charging(0); disable_sleep(SLEEP_MASK_CHARGING); while (1) { last_waken = get_time(); pmu_clear_irq(); #ifdef CONFIG_PMU_TPS65090_CHARGING_LED update_battery_led(); #endif /* * When battery is extremely low, the internal voltage can not * power on its gas gauge IC. Charging loop will enable the * charger and turn on trickle charging. For safety reason, * charger should be disabled if the communication to battery * failed. */ if (current_state == ST_PRE_CHARGING && get_time().val - pre_chg_start.val >= PRE_CHARGING_TIMEOUT) next_state = ST_CHARGING_ERROR; else next_state = calc_next_state(current_state); if (next_state != current_state) { /* Reset state of charge moving average window */ rsoc_moving_average(-1); CPRINTS("batt state %s -> %s", state_list[current_state], state_list[next_state]); current_state = next_state; switch (current_state) { case ST_PRE_CHARGING: pre_chg_start = get_time(); /* Fall through */ case ST_CHARGING: if (pmu_blink_led(0)) next_state = ST_CHARGING_ERROR; else enable_charging(1); break; case ST_CHARGING_ERROR: /* * Enable hardware charging circuit after set * PMU to hardware error state. */ if (pmu_blink_led(1)) enable_charging(0); else enable_charging(1); break; case ST_IDLE: case ST_IDLE0: case ST_BAD_COND: case ST_DISCHARGING: enable_charging(0); /* Ignore charger error when discharging */ pmu_blink_led(0); break; } } switch (current_state) { case ST_CHARGING: case ST_CHARGING_ERROR: wait_time = T2_USEC; break; case ST_DISCHARGING: wait_time = T3_USEC; break; case ST_PRE_CHARGING: wait_time = T1_USEC; if (get_time().val - pre_chg_start.val >= PRE_CHARGING_TIMEOUT) enable_charging(0); break; default: if (extpower_is_present()) { wait_time = T1_USEC; break; } else if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { wait_time = T1_OFF_USEC; enable_sleep(SLEEP_MASK_CHARGING); } else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) { wait_time = T1_SUSPEND_USEC; } else { wait_time = T1_USEC; } } if (!has_pending_event) { task_wait_event(wait_time); disable_sleep(SLEEP_MASK_CHARGING); } else { has_pending_event = 0; } } } void pmu_task_throttled_wake(void) { timestamp_t now = get_time(); if (now.val - last_waken.val >= HOOK_TICK_INTERVAL) { has_pending_event = 0; task_wake(TASK_ID_CHARGER); } else { has_pending_event = 1; } } static void wake_pmu_task_if_necessary(void) { if (has_pending_event) { has_pending_event = 0; task_wake(TASK_ID_CHARGER); } } DECLARE_HOOK(HOOK_TICK, wake_pmu_task_if_necessary, HOOK_PRIO_DEFAULT); /* Wake charging task on chipset events */ static void pmu_chipset_events(void) { pmu_task_throttled_wake(); } DECLARE_HOOK(HOOK_CHIPSET_STARTUP, pmu_chipset_events, HOOK_PRIO_DEFAULT); DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, pmu_chipset_events, HOOK_PRIO_DEFAULT); DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, pmu_chipset_events, HOOK_PRIO_DEFAULT); DECLARE_HOOK(HOOK_CHIPSET_RESUME, pmu_chipset_events, HOOK_PRIO_DEFAULT); void pmu_irq_handler(enum gpio_signal signal) { pmu_task_throttled_wake(); CPRINTS("Charger IRQ received"); }