/* Setup init condition */ static void test_setup(int on_ac) { const struct battery_info *bat_info = battery_get_info(); reset_mocks(); /* 50% of charge */ sb_write(SB_RELATIVE_STATE_OF_CHARGE, 50); sb_write(SB_ABSOLUTE_STATE_OF_CHARGE, 50); /* full charge capacity in mAh */ sb_write(SB_FULL_CHARGE_CAPACITY, 0xf000); /* 25 degree Celsius */ sb_write(SB_TEMPERATURE, CELSIUS_TO_DECI_KELVIN(25)); /* battery pack voltage */ sb_write(SB_VOLTAGE, bat_info->voltage_normal); /* desired charging voltage/current */ sb_write(SB_CHARGING_VOLTAGE, bat_info->voltage_max); sb_write(SB_CHARGING_CURRENT, 4000); /* battery pack current is positive when charging */ if (on_ac) { sb_write(SB_CURRENT, 1000); gpio_set_level(GPIO_AC_PRESENT, 1); } else { sb_write(SB_CURRENT, -100); gpio_set_level(GPIO_AC_PRESENT, 0); } /* Reset the charger state to initial state */ charge_control(CHARGE_CONTROL_NORMAL); /* Let things stabilize */ wait_charging_state(); }
static int test_high_temp_battery(void) { test_setup(1); ccprintf("[CHARGING TEST] High battery temperature shutdown\n"); ev_clear(EC_HOST_EVENT_BATTERY_SHUTDOWN); sb_write(SB_TEMPERATURE, CELSIUS_TO_DECI_KELVIN(90)); wait_charging_state(); TEST_ASSERT(ev_is_set(EC_HOST_EVENT_BATTERY_SHUTDOWN)); TEST_ASSERT(!is_shutdown); sleep(CONFIG_BATTERY_CRITICAL_SHUTDOWN_TIMEOUT); TEST_ASSERT(is_shutdown); ccprintf("[CHARGING TEST] High battery temp S0->S5 hibernate\n"); mock_chipset_state = CHIPSET_STATE_SOFT_OFF; wait_charging_state(); TEST_ASSERT(is_hibernated); return EC_SUCCESS; }
static int test_charge_state(void) { enum charge_state state; uint32_t flags; /* On AC */ test_setup(1); ccprintf("[CHARGING TEST] AC on\n"); /* Detach battery, charging error */ ccprintf("[CHARGING TEST] Detach battery\n"); TEST_ASSERT(test_detach_i2c(I2C_PORT_BATTERY, BATTERY_ADDR) == EC_SUCCESS); msleep(BATTERY_DETACH_DELAY); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_ERROR); /* Attach battery again, charging */ ccprintf("[CHARGING TEST] Attach battery\n"); test_attach_i2c(I2C_PORT_BATTERY, BATTERY_ADDR); /* And changing full capacity should trigger a host event */ ev_clear(EC_HOST_EVENT_BATTERY); sb_write(SB_FULL_CHARGE_CAPACITY, 0xeff0); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_CHARGE); TEST_ASSERT(ev_is_set(EC_HOST_EVENT_BATTERY)); /* Unplug AC, discharging at 1000mAh */ ccprintf("[CHARGING TEST] AC off\n"); gpio_set_level(GPIO_AC_PRESENT, 0); sb_write(SB_CURRENT, -1000); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_DISCHARGE); flags = charge_get_flags(); TEST_ASSERT(!(flags & CHARGE_FLAG_EXTERNAL_POWER)); TEST_ASSERT(!(flags & CHARGE_FLAG_FORCE_IDLE)); /* Discharging waaaay overtemp is ignored */ ccprintf("[CHARGING TEST] AC off, batt temp = 0xffff\n"); gpio_set_level(GPIO_AC_PRESENT, 0); sb_write(SB_CURRENT, -1000); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_DISCHARGE); sb_write(SB_TEMPERATURE, 0xffff); state = wait_charging_state(); TEST_ASSERT(!is_shutdown); TEST_ASSERT(state == PWR_STATE_DISCHARGE); sb_write(SB_TEMPERATURE, CELSIUS_TO_DECI_KELVIN(40)); /* Discharging overtemp */ ccprintf("[CHARGING TEST] AC off, batt temp = 90 C\n"); gpio_set_level(GPIO_AC_PRESENT, 0); sb_write(SB_CURRENT, -1000); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_DISCHARGE); sb_write(SB_TEMPERATURE, CELSIUS_TO_DECI_KELVIN(90)); state = wait_charging_state(); TEST_ASSERT(is_shutdown); TEST_ASSERT(state == PWR_STATE_DISCHARGE); sb_write(SB_TEMPERATURE, CELSIUS_TO_DECI_KELVIN(40)); /* Force idle */ ccprintf("[CHARGING TEST] AC on, force idle\n"); gpio_set_level(GPIO_AC_PRESENT, 1); sb_write(SB_CURRENT, 1000); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_CHARGE); flags = charge_get_flags(); TEST_ASSERT(flags & CHARGE_FLAG_EXTERNAL_POWER); TEST_ASSERT(!(flags & CHARGE_FLAG_FORCE_IDLE)); charge_control(CHARGE_CONTROL_IDLE); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_IDLE); flags = charge_get_flags(); TEST_ASSERT(flags & CHARGE_FLAG_EXTERNAL_POWER); TEST_ASSERT(flags & CHARGE_FLAG_FORCE_IDLE); charge_control(CHARGE_CONTROL_NORMAL); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_CHARGE); /* Force discharge */ ccprintf("[CHARGING TEST] AC on, force discharge\n"); gpio_set_level(GPIO_AC_PRESENT, 1); sb_write(SB_CURRENT, 1000); charge_control(CHARGE_CONTROL_DISCHARGE); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_IDLE); TEST_ASSERT(is_force_discharge); charge_control(CHARGE_CONTROL_NORMAL); state = wait_charging_state(); TEST_ASSERT(state == PWR_STATE_CHARGE); TEST_ASSERT(!is_force_discharge); return EC_SUCCESS; }
/* Main loop */ void charger_task(void) { int sleep_usec; int need_static = 1; const struct charger_info * const info = charger_get_info(); /* Get the battery-specific values */ batt_info = battery_get_info(); prev_ac = prev_charge = -1; chg_ctl_mode = CHARGE_CONTROL_NORMAL; shutdown_warning_time.val = 0UL; battery_seems_to_be_dead = 0; /* * If system is not locked and we don't have a battery to live on, * then use max input current limit so that we can pull as much power * as needed. */ battery_get_params(&curr.batt); prev_bp = curr.batt.is_present; curr.desired_input_current = get_desired_input_current(prev_bp, info); while (1) { #ifdef CONFIG_SB_FIRMWARE_UPDATE if (sb_fw_update_in_progress()) { task_wait_event(CHARGE_MAX_SLEEP_USEC); continue; } #endif /* Let's see what's going on... */ curr.ts = get_time(); sleep_usec = 0; problems_exist = 0; curr.ac = extpower_is_present(); if (curr.ac != prev_ac) { if (curr.ac) { /* * Some chargers are unpowered when the AC is * off, so we'll reinitialize it when AC * comes back and set the input current limit. * Try again if it fails. */ int rv = charger_post_init(); if (rv != EC_SUCCESS) { problem(PR_POST_INIT, rv); } else { if (curr.desired_input_current != CHARGE_CURRENT_UNINITIALIZED) rv = charger_set_input_current( curr.desired_input_current); if (rv != EC_SUCCESS) problem(PR_SET_INPUT_CURR, rv); else prev_ac = curr.ac; } } else { /* Some things are only meaningful on AC */ chg_ctl_mode = CHARGE_CONTROL_NORMAL; battery_seems_to_be_dead = 0; prev_ac = curr.ac; } } charger_get_params(&curr.chg); battery_get_params(&curr.batt); if (prev_bp != curr.batt.is_present) { prev_bp = curr.batt.is_present; /* Update battery info due to change of battery */ batt_info = battery_get_info(); need_static = 1; curr.desired_input_current = get_desired_input_current(prev_bp, info); if (curr.desired_input_current != CHARGE_CURRENT_UNINITIALIZED) charger_set_input_current( curr.desired_input_current); hook_notify(HOOK_BATTERY_SOC_CHANGE); } /* * TODO(crosbug.com/p/27527). Sometimes the battery thinks its * temperature is 6280C, which seems a bit high. Let's ignore * anything above the boiling point of tungsten until this bug * is fixed. If the battery is really that warm, we probably * have more urgent problems. */ if (curr.batt.temperature > CELSIUS_TO_DECI_KELVIN(5660)) { CPRINTS("ignoring ridiculous batt.temp of %dC", DECI_KELVIN_TO_CELSIUS(curr.batt.temperature)); curr.batt.flags |= BATT_FLAG_BAD_TEMPERATURE; } /* If the battery thinks it's above 100%, don't believe it */ if (curr.batt.state_of_charge > 100) { CPRINTS("ignoring ridiculous batt.soc of %d%%", curr.batt.state_of_charge); curr.batt.flags |= BATT_FLAG_BAD_STATE_OF_CHARGE; } /* * Now decide what we want to do about it. We'll normally just * pass along whatever the battery wants to the charger. Note * that if battery_get_params() can't get valid values from the * battery it uses (0, 0), which is probably safer than blindly * applying power to a battery we can't talk to. */ curr.requested_voltage = curr.batt.desired_voltage; curr.requested_current = curr.batt.desired_current; /* If we *know* there's no battery, wait for one to appear. */ if (curr.batt.is_present == BP_NO) { ASSERT(curr.ac); /* How are we running? */ curr.state = ST_IDLE; curr.batt_is_charging = 0; battery_was_removed = 1; goto wait_for_it; } /* * If we had trouble talking to the battery or the charger, we * should probably do nothing for a bit, and if it doesn't get * better then flag it as an error. */ if (curr.chg.flags & CHG_FLAG_BAD_ANY) problem(PR_CHG_FLAGS, curr.chg.flags); if (curr.batt.flags & BATT_FLAG_BAD_ANY) problem(PR_BATT_FLAGS, curr.batt.flags); /* * If AC is present, check if input current is sufficient to * actually charge battery. */ curr.batt_is_charging = curr.ac && (curr.batt.current >= 0); /* Don't let the battery hurt itself. */ shutdown_on_critical_battery(); if (!curr.ac) { curr.state = ST_DISCHARGE; goto wait_for_it; } /* Okay, we're on AC and we should have a battery. */ /* Used for factory tests. */ if (chg_ctl_mode != CHARGE_CONTROL_NORMAL) { curr.state = ST_IDLE; goto wait_for_it; } /* If the battery is not responsive, try to wake it up. */ if (!(curr.batt.flags & BATT_FLAG_RESPONSIVE)) { if (battery_seems_to_be_dead || battery_is_cut_off()) { /* It's dead, do nothing */ curr.state = ST_IDLE; curr.requested_voltage = 0; curr.requested_current = 0; } else if (curr.state == ST_PRECHARGE && (get_time().val > precharge_start_time.val + PRECHARGE_TIMEOUT_US)) { /* We've tried long enough, give up */ CPRINTS("battery seems to be dead"); battery_seems_to_be_dead = 1; curr.state = ST_IDLE; curr.requested_voltage = 0; curr.requested_current = 0; } else { /* See if we can wake it up */ if (curr.state != ST_PRECHARGE) { CPRINTS("try to wake battery"); precharge_start_time = get_time(); need_static = 1; } curr.state = ST_PRECHARGE; curr.requested_voltage = batt_info->voltage_max; curr.requested_current = batt_info->precharge_current; } goto wait_for_it; } else { /* The battery is responding. Yay. Try to use it. */ #ifdef CONFIG_BATTERY_REQUESTS_NIL_WHEN_DEAD /* * TODO (crosbug.com/p/29467): remove this workaround * for dead battery that requests no voltage/current */ if (curr.requested_voltage == 0 && curr.requested_current == 0 && curr.batt.state_of_charge == 0) { /* Battery is dead, give precharge current */ curr.requested_voltage = batt_info->voltage_max; curr.requested_current = batt_info->precharge_current; } else #endif #ifdef CONFIG_BATTERY_REVIVE_DISCONNECT battery_seems_to_be_disconnected = 0; if (curr.requested_voltage == 0 && curr.requested_current == 0 && battery_get_disconnect_state() == BATTERY_DISCONNECTED) { /* * Battery is in disconnect state. Apply a * current to kick it out of this state. */ CPRINTS("found battery in disconnect state"); curr.requested_voltage = batt_info->voltage_max; curr.requested_current = batt_info->precharge_current; battery_seems_to_be_disconnected = 1; } else #endif if (curr.state == ST_PRECHARGE || battery_seems_to_be_dead || battery_was_removed) { CPRINTS("battery woke up"); /* Update the battery-specific values */ batt_info = battery_get_info(); need_static = 1; } battery_seems_to_be_dead = battery_was_removed = 0; curr.state = ST_CHARGE; } /* * TODO(crosbug.com/p/27643): Quit trying if charging too long * without getting full (CONFIG_CHARGER_TIMEOUT_HOURS). */ wait_for_it: #ifdef CONFIG_CHARGER_PROFILE_OVERRIDE sleep_usec = charger_profile_override(&curr); if (sleep_usec < 0) problem(PR_CUSTOM, sleep_usec); #endif /* Keep the AP informed */ if (need_static) need_static = update_static_battery_info(); /* Wait on the dynamic info until the static info is good. */ if (!need_static) update_dynamic_battery_info(); notify_host_of_low_battery(); /* And the EC console */ is_full = calc_is_full(); if ((!(curr.batt.flags & BATT_FLAG_BAD_STATE_OF_CHARGE) && curr.batt.state_of_charge != prev_charge) || (is_full != prev_full)) { show_charging_progress(); prev_charge = curr.batt.state_of_charge; hook_notify(HOOK_BATTERY_SOC_CHANGE); } prev_full = is_full; /* Turn charger off if it's not needed */ if (curr.state == ST_IDLE || curr.state == ST_DISCHARGE) { curr.requested_voltage = 0; curr.requested_current = 0; } /* Apply external limits */ if (curr.requested_current > user_current_limit) curr.requested_current = user_current_limit; /* Round to valid values */ curr.requested_voltage = charger_closest_voltage(curr.requested_voltage); curr.requested_current = charger_closest_current(curr.requested_current); /* Charger only accpets request when AC is on. */ if (curr.ac) { /* * Some batteries would wake up after cut-off if we keep * charging it. Thus, we only charge when AC is on and * battery is not cut off yet. */ if (battery_is_cut_off()) charge_request(0, 0); /* * As a safety feature, some chargers will stop * charging if we don't communicate with it frequently * enough. In manual mode, we'll just tell it what it * knows. */ else if (manual_mode) { charge_request(curr.chg.voltage, curr.chg.current); } else { charge_request(curr.requested_voltage, curr.requested_current); } } else { charge_request( charger_closest_voltage( curr.batt.voltage + info->voltage_step), -1); } /* How long to sleep? */ if (problems_exist) /* If there are errors, don't wait very long. */ sleep_usec = CHARGE_POLL_PERIOD_SHORT; else if (sleep_usec <= 0) { /* default values depend on the state */ if (curr.state == ST_IDLE || curr.state == ST_DISCHARGE) { /* If AP is off, we can sleep a long time */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF | CHIPSET_STATE_SUSPEND)) sleep_usec = CHARGE_POLL_PERIOD_VERY_LONG; else /* Discharging, not too urgent */ sleep_usec = CHARGE_POLL_PERIOD_LONG; } else { /* Charging, so pay closer attention */ sleep_usec = CHARGE_POLL_PERIOD_CHARGE; } } /* Adjust for time spent in this loop */ sleep_usec -= (int)(get_time().val - curr.ts.val); if (sleep_usec < CHARGE_MIN_SLEEP_USEC) sleep_usec = CHARGE_MIN_SLEEP_USEC; else if (sleep_usec > CHARGE_MAX_SLEEP_USEC) sleep_usec = CHARGE_MAX_SLEEP_USEC; task_wait_event(sleep_usec); } }
static int acpi_thermal_trips_update(struct acpi_thermal *tz, int flag) { acpi_status status = AE_OK; unsigned long long tmp; struct acpi_handle_list devices; int valid = 0; int i; /* Critical Shutdown */ if (flag & ACPI_TRIPS_CRITICAL) { status = acpi_evaluate_integer(tz->device->handle, "_CRT", NULL, &tmp); tz->trips.critical.temperature = tmp; /* * Treat freezing temperatures as invalid as well; some * BIOSes return really low values and cause reboots at startup. * Below zero (Celsius) values clearly aren't right for sure.. * ... so lets discard those as invalid. */ if (ACPI_FAILURE(status)) { tz->trips.critical.flags.valid = 0; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No critical threshold\n")); } else if (tmp <= 2732) { pr_warn(FW_BUG "Invalid critical threshold (%llu)\n", tmp); tz->trips.critical.flags.valid = 0; } else { tz->trips.critical.flags.valid = 1; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found critical threshold [%lu]\n", tz->trips.critical.temperature)); } if (tz->trips.critical.flags.valid == 1) { if (crt == -1) { tz->trips.critical.flags.valid = 0; } else if (crt > 0) { unsigned long crt_k = CELSIUS_TO_DECI_KELVIN(crt); /* * Allow override critical threshold */ if (crt_k > tz->trips.critical.temperature) pr_warn(PREFIX "Critical threshold %d C\n", crt); tz->trips.critical.temperature = crt_k; } } } /* Critical Sleep (optional) */ if (flag & ACPI_TRIPS_HOT) { status = acpi_evaluate_integer(tz->device->handle, "_HOT", NULL, &tmp); if (ACPI_FAILURE(status)) { tz->trips.hot.flags.valid = 0; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "No hot threshold\n")); } else { tz->trips.hot.temperature = tmp; tz->trips.hot.flags.valid = 1; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Found hot threshold [%lu]\n", tz->trips.hot.temperature)); } } /* Passive (optional) */ if (((flag & ACPI_TRIPS_PASSIVE) && tz->trips.passive.flags.valid) || (flag == ACPI_TRIPS_INIT)) { valid = tz->trips.passive.flags.valid; if (psv == -1) { status = AE_SUPPORT; } else if (psv > 0) { tmp = CELSIUS_TO_DECI_KELVIN(psv); status = AE_OK; } else { status = acpi_evaluate_integer(tz->device->handle, "_PSV", NULL, &tmp); } if (ACPI_FAILURE(status)) tz->trips.passive.flags.valid = 0; else { tz->trips.passive.temperature = tmp; tz->trips.passive.flags.valid = 1; if (flag == ACPI_TRIPS_INIT) { status = acpi_evaluate_integer( tz->device->handle, "_TC1", NULL, &tmp); if (ACPI_FAILURE(status)) tz->trips.passive.flags.valid = 0; else tz->trips.passive.tc1 = tmp; status = acpi_evaluate_integer( tz->device->handle, "_TC2", NULL, &tmp); if (ACPI_FAILURE(status)) tz->trips.passive.flags.valid = 0; else tz->trips.passive.tc2 = tmp; status = acpi_evaluate_integer( tz->device->handle, "_TSP", NULL, &tmp); if (ACPI_FAILURE(status)) tz->trips.passive.flags.valid = 0; else tz->trips.passive.tsp = tmp; } } } if ((flag & ACPI_TRIPS_DEVICES) && tz->trips.passive.flags.valid) { memset(&devices, 0, sizeof(struct acpi_handle_list)); status = acpi_evaluate_reference(tz->device->handle, "_PSL", NULL, &devices); if (ACPI_FAILURE(status)) { pr_warn(PREFIX "Invalid passive threshold\n"); tz->trips.passive.flags.valid = 0; } else tz->trips.passive.flags.valid = 1; if (memcmp(&tz->trips.passive.devices, &devices, sizeof(struct acpi_handle_list))) { memcpy(&tz->trips.passive.devices, &devices, sizeof(struct acpi_handle_list)); ACPI_THERMAL_TRIPS_EXCEPTION(flag, "device"); } } if ((flag & ACPI_TRIPS_PASSIVE) || (flag & ACPI_TRIPS_DEVICES)) { if (valid != tz->trips.passive.flags.valid) ACPI_THERMAL_TRIPS_EXCEPTION(flag, "state"); } /* Active (optional) */ for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) { char name[5] = { '_', 'A', 'C', ('0' + i), '\0' }; valid = tz->trips.active[i].flags.valid; if (act == -1) break; /* disable all active trip points */ if ((flag == ACPI_TRIPS_INIT) || ((flag & ACPI_TRIPS_ACTIVE) && tz->trips.active[i].flags.valid)) { status = acpi_evaluate_integer(tz->device->handle, name, NULL, &tmp); if (ACPI_FAILURE(status)) { tz->trips.active[i].flags.valid = 0; if (i == 0) break; if (act <= 0) break; if (i == 1) tz->trips.active[0].temperature = CELSIUS_TO_DECI_KELVIN(act); else /* * Don't allow override higher than * the next higher trip point */ tz->trips.active[i - 1].temperature = (tz->trips.active[i - 2].temperature < CELSIUS_TO_DECI_KELVIN(act) ? tz->trips.active[i - 2].temperature : CELSIUS_TO_DECI_KELVIN(act)); break; } else { tz->trips.active[i].temperature = tmp; tz->trips.active[i].flags.valid = 1; } } name[2] = 'L'; if ((flag & ACPI_TRIPS_DEVICES) && tz->trips.active[i].flags.valid ) { memset(&devices, 0, sizeof(struct acpi_handle_list)); status = acpi_evaluate_reference(tz->device->handle, name, NULL, &devices); if (ACPI_FAILURE(status)) { pr_warn(PREFIX "Invalid active%d threshold\n", i); tz->trips.active[i].flags.valid = 0; } else tz->trips.active[i].flags.valid = 1; if (memcmp(&tz->trips.active[i].devices, &devices, sizeof(struct acpi_handle_list))) { memcpy(&tz->trips.active[i].devices, &devices, sizeof(struct acpi_handle_list)); ACPI_THERMAL_TRIPS_EXCEPTION(flag, "device"); } } if ((flag & ACPI_TRIPS_ACTIVE) || (flag & ACPI_TRIPS_DEVICES)) if (valid != tz->trips.active[i].flags.valid) ACPI_THERMAL_TRIPS_EXCEPTION(flag, "state"); if (!tz->trips.active[i].flags.valid) break; } if ((flag & ACPI_TRIPS_DEVICES) && acpi_has_method(tz->device->handle, "_TZD")) { memset(&devices, 0, sizeof(devices)); status = acpi_evaluate_reference(tz->device->handle, "_TZD", NULL, &devices); if (ACPI_SUCCESS(status) && memcmp(&tz->devices, &devices, sizeof(devices))) { tz->devices = devices; ACPI_THERMAL_TRIPS_EXCEPTION(flag, "device"); } } return 0; }