static int command_power(int argc, char **argv) { int v; if (argc < 2) { enum power_state_t state; state = PSTATE_UNKNOWN; if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) state = PSTATE_OFF; if (chipset_in_state(CHIPSET_STATE_SUSPEND)) state = PSTATE_SUSPEND; if (chipset_in_state(CHIPSET_STATE_ON)) state = PSTATE_ON; ccprintf("%s\n", state_name[state]); return EC_SUCCESS; } if (!parse_bool(argv[1], &v)) return EC_ERROR_PARAM1; power_request = v ? POWER_REQ_ON : POWER_REQ_OFF; ccprintf("Requesting power %s\n", power_req_name[power_request]); task_wake(TASK_ID_CHIPSET); return EC_SUCCESS; }
static void oak_led_set_power(int board_version) { static int power_second; power_second++; switch(board_version) { case OAK_REV3: case OAK_REV4: /* * For Rev3 and Rev4 revision. * PWR LED behavior: * Power on: Green ON * Suspend: Orange in breeze mode ( 1 sec on/ 3 sec off) * Power off: OFF */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { bat_led_set(PWR_LED_GREEN, 0); bat_led_set(PWR_LED_ORANGE, 0); } else if (chipset_in_state(CHIPSET_STATE_ON)) { bat_led_set(PWR_LED_GREEN, 1); bat_led_set(PWR_LED_ORANGE, 0); } else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) { bat_led_set(PWR_LED_GREEN, 0); bat_led_set(PWR_LED_ORANGE, (power_second & 3) ? 0 : 1); } break; default: break; } }
/* * In AP S0ix & S3 -> S0 transitions, * the chipset_resume hook is called. * * During S0ix exit, the wake mask for lid open is disabled. * All pending events are cleared * */ void lpc_disable_wake_mask_for_lid_open(void) { if ((chipset_in_state(CHIPSET_STATE_STANDBY | CHIPSET_STATE_ON)) || chipset_in_state(CHIPSET_STATE_ON)) { lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, 0); lpc_clear_host_events(); } }
/* * In AP S0 -> S3 & S0ix transitions, * the chipset_suspend is called. * * The chipset_in_state(CHIPSET_STATE_STANDBY | CHIPSET_STATE_ON) * is used to detect the S0ix transiton. * * During S0ix entry, the wake mask for lid open is enabled. * */ void lpc_enable_wake_mask_for_lid_open(void) { if ((chipset_in_state(CHIPSET_STATE_STANDBY | CHIPSET_STATE_ON)) || chipset_in_state(CHIPSET_STATE_STANDBY)) { uint32_t mask = 0; mask = ((lpc_get_host_event_mask(LPC_HOST_EVENT_WAKE)) | EC_HOST_EVENT_MASK(EC_HOST_EVENT_LID_OPEN)); lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, mask); } }
/* * Send host event to the AP if the battery is temperature or charge level * is critical. Force-shutdown if the problem isn't corrected after timeout. */ static void shutdown_on_critical_battery(void) { int batt_temp_c; int battery_critical = 0; /* * TODO(crosbug.com/p/27642): The thermal loop should watch the battery * temp, so it can turn fans on. */ batt_temp_c = DECI_KELVIN_TO_CELSIUS(curr.batt.temperature); if (battery_too_hot(batt_temp_c)) { CPRINTS("Batt temp out of range: %dC", batt_temp_c); battery_critical = 1; } if (battery_too_low() && !curr.batt_is_charging) { CPRINTS("Low battery: %d%%, %dmV", curr.batt.state_of_charge, curr.batt.voltage); battery_critical = 1; } if (!battery_critical) { /* Reset shutdown warning time */ shutdown_warning_time.val = 0; return; } if (!shutdown_warning_time.val) { CPRINTS("charge warn shutdown due to critical battery"); shutdown_warning_time = get_time(); if (!chipset_in_state(CHIPSET_STATE_ANY_OFF)) host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN); } else if (get_time().val > shutdown_warning_time.val + CRITICAL_BATTERY_SHUTDOWN_TIMEOUT_US) { if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { #ifdef CONFIG_HIBERNATE /* Timeout waiting for charger to provide more power */ CPRINTS( "charge force EC hibernate due to critical battery"); system_hibernate(0, 0); #elif defined(CONFIG_BATTERY_CRITICAL_SHUTDOWN_CUT_OFF) CPRINTS( "charge force battery cut-off due to critical level"); board_cut_off_battery(); #endif } else { /* Timeout waiting for AP to shut down, so kill it */ CPRINTS( "charge force shutdown due to critical battery"); chipset_force_shutdown(); } } }
static void std_led_set_power(void) { static int power_second; power_second++; if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) pwr_led_set_color(LED_OFF); else if (chipset_in_state(CHIPSET_STATE_ON)) pwr_led_set_color(LED_WHITE); else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) pwr_led_set_color((power_second & 3) ? LED_OFF : LED_WHITE); }
static void spi_init(void) { stm32_spi_regs_t *spi = STM32_SPI1_REGS; /* Reset the SPI Peripheral to clear any existing weird states. */ /* Fix for bug chrome-os-partner:31390 */ enabled = 0; state = SPI_STATE_DISABLED; STM32_RCC_APB2RSTR |= (1 << 12); STM32_RCC_APB2RSTR &= ~(1 << 12); /* 40 MHz pin speed */ STM32_GPIO_OSPEEDR(GPIO_A) |= 0xff00; /* Enable clocks to SPI1 module */ STM32_RCC_APB2ENR |= STM32_RCC_PB2_SPI1; /* * Enable rx/tx DMA and get ready to receive our first transaction and * "disable" FIFO by setting event to happen after only 1 byte */ spi->cr2 = STM32_SPI_CR2_RXDMAEN | STM32_SPI_CR2_TXDMAEN | STM32_SPI_CR2_FRXTH; /* Enable the SPI peripheral */ spi->cr1 |= STM32_SPI_CR1_SPE; gpio_enable_interrupt(GPIO_SPI1_NSS); /* If chipset is already on, prepare for transactions */ if (chipset_in_state(CHIPSET_STATE_ON)) spi_chipset_startup(); }
int peci_temp_sensor_get_val(int idx, int *temp_ptr) { int sum = 0; int success_cnt = 0; int i; if (!chipset_in_state(CHIPSET_STATE_ON)) return EC_ERROR_NOT_POWERED; for (i = 0; i < TEMP_AVG_LENGTH; ++i) { if (temp_vals[i] >= 0) { success_cnt++; sum += temp_vals[i]; } } /* * Require at least two valid samples. When the AP transitions into S0, * it is possible, depending on the timing of the PECI sample, to read * an invalid temperature. This is very rare, but when it does happen * the temperature returned is CONFIG_PECI_TJMAX. Requiring two valid * samples here assures us that one bad maximum temperature reading * when entering S0 won't cause us to trigger an over temperature. */ if (success_cnt < 2) return EC_ERROR_UNKNOWN; *temp_ptr = sum / success_cnt; return EC_SUCCESS; }
static void kevin_led_set_power_battery(void) { static int power_ticks; if (chipset_in_state(CHIPSET_STATE_ON)) { set_color(LED_BLUE); return; } /* CHIPSET_STATE_OFF */ switch (charge_get_state()) { case PWR_STATE_DISCHARGE: set_color(LED_OFF); break; case PWR_STATE_CHARGE: set_color(LED_RED); break; case PWR_STATE_ERROR: power_ticks++; set_color(((power_ticks % LED_TOTAL_TICKS) < LED_ON_TICKS) ? LED_RED : LED_GREEN); break; case PWR_STATE_CHARGE_NEAR_FULL: case PWR_STATE_IDLE: /* External power connected in IDLE. */ set_color(LED_GREEN); break; default: set_color(LED_RED); break; } if ((charge_get_state()) != PWR_STATE_ERROR) power_ticks = 0; }
static void big_led_set_power(void) { static int power_second; power_second++; /* PWR LED behavior: * Power on: Blue * Suspend: Orange in breeze mode ( 1 sec on/ 3 sec off) * Power off: OFF */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) pwr_led_set_color(LED_OFF); else if (chipset_in_state(CHIPSET_STATE_ON)) pwr_led_set_color(LED_BLUE); else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) pwr_led_set_color((power_second & 3) ? LED_OFF : LED_ORANGE); }
static void jerry_led_set_power(void) { static int power_second; power_second++; /* PWR LED behavior: * Power on: Green * Suspend: Green in breeze mode ( 1 sec on/ 3 sec off) * Power off: OFF */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) pwr_led_set(0); else if (chipset_in_state(CHIPSET_STATE_ON)) pwr_led_set(1); else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) pwr_led_set((power_second & 3) ? 0 : 1); }
/** * 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; }
static void motion_sense_startup(void) { int i; struct motion_sensor_t *sensor; sensor_active = SENSOR_ACTIVE_S5; for (i = 0; i < motion_sensor_count; ++i) { sensor = &motion_sensors[i]; sensor->state = SENSOR_NOT_INITIALIZED; } /* If the AP is already in S0, call the resume hook now. * We may initialize the sensor 2 times (once in RO, anoter time in RW), * but it may be necessary if the init sequence has changed. */ if (chipset_in_state(SENSOR_ACTIVE_S0_S3)) motion_sense_suspend(); if (chipset_in_state(SENSOR_ACTIVE_S0)) motion_sense_resume(); }
/* * In AP S0ix & S3 -> S0 transitions, * the chipset_resume hook is called. * * During S0ix exit, the wake mask for lid open is disabled. * All pending events are cleared */ void lpc_disable_wake_mask_for_lid_open(void) { if (chipset_in_state(CHIPSET_STATE_STANDBY | CHIPSET_STATE_ON)) { uint32_t mask; mask = lpc_get_host_event_mask(LPC_HOST_EVENT_WAKE) & ~EC_HOST_EVENT_MASK(EC_HOST_EVENT_LID_OPEN); lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, mask); lpc_clear_host_events(); } }
static void lpc_resume(void) { #ifdef CONFIG_POWER_S0IX if (chipset_in_state(CHIPSET_STATE_SUSPEND | CHIPSET_STATE_ON)) #endif { /* Mask all host events until the host unmasks them itself. */ lpc_set_host_event_mask(LPC_HOST_EVENT_SMI, 0); lpc_set_host_event_mask(LPC_HOST_EVENT_SCI, 0); lpc_set_host_event_mask(LPC_HOST_EVENT_WAKE, 0); } /* Store port 80 event so we know where resume happened */ port_80_write(PORT_80_EVENT_RESUME); }
/** * 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; }
/* Enable or disable input devices, based upon chipset state and tablet mode */ static void enable_input_devices(void) { int kb_enable = 1; int tp_enable = 1; /* Disable KB & TP if chipset is off */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { kb_enable = 0; tp_enable = 0; } keyboard_scan_enable(kb_enable, KB_SCAN_DISABLE_LID_ANGLE); gpio_set_level(GPIO_EN_P3300_TRACKPAD_ODL, !tp_enable); }
/* Enable or disable input devices, based upon chipset state and tablet mode */ static void enable_input_devices(void) { int kb_enable = 1; int tp_enable = 1; /* Disable both TP and KB in tablet mode */ if (!gpio_get_level(GPIO_TABLET_MODE_L)) kb_enable = tp_enable = 0; /* Disable TP if chipset is off */ else if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) tp_enable = 0; keyboard_scan_enable(kb_enable, KB_SCAN_DISABLE_LID_ANGLE); gpio_set_level(GPIO_ENABLE_TOUCHPAD, tp_enable); }
void chipset_force_shutdown(void) { CPRINTS("%s()", __func__); /* * Force off. Sending a reset command to the PMIC will power off * the EC, so simulate a long power button press instead. This * condition will reset once the state machine transitions to G3. * Consider reducing the latency here by changing the power off * hold time on the PMIC. */ if (!chipset_in_state(CHIPSET_STATE_HARD_OFF)) { forcing_shutdown = 1; power_button_pch_press(); } }
int charger_profile_override(struct charge_state_data *curr) { if (override_voltage) curr->requested_voltage = override_voltage; if (override_current) curr->requested_current = override_current; if (override_usec) return override_usec; /* Don't let it sleep a whole minute when the AP is off */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) return CHARGE_POLL_PERIOD_LONG; return 0; }
/** * Setup backlight controller and turn it on. */ static void lp8555_enable_pwm_mode(void) { int reg; int rv; /* * If not in S0, then PCH backlight enable will not be on, and if * lid is closed EC backlight enable will not be on. Since these * two signals are AND'ed together, no point in trying to talk to * the lp8555 if either one of them is not true. */ if (!chipset_in_state(CHIPSET_STATE_ON) || !lid_is_open()) return; /* Enable PWM mode. */ rv = lp8555_read_with_retry(LP8555_REG_CONFIG, ®); if (rv != EC_SUCCESS) return; reg &= ~LP8555_REG_CONFIG_MODE_MASK; reg |= LP8555_REG_CONFIG_MODE_PWM; rv = lp8555_write_with_retry(LP8555_REG_CONFIG, reg); if (rv != EC_SUCCESS) return; /* Set max LED current to 23mA. */ rv = lp8555_write_with_retry(LP8555_REG_CURRENT, LP8555_REG_CURRENT_MAXCURR_23MA); if (rv != EC_SUCCESS) return; /* Set the rate of brightness change. */ rv = lp8555_write_with_retry(LP8555_REG_STEP, LP8555_REG_STEP_STEP_200MS | LP8555_REG_STEP_PWM_IN_HYST_8LSB | LP8555_REG_STEP_SMOOTH_HEAVY); if (rv != EC_SUCCESS) return; /* Power on. */ rv = lp8555_read_with_retry(LP8555_REG_COMMAND, ®); if (rv != EC_SUCCESS) return; reg |= LP8555_REG_COMMAND_ON; rv = lp8555_write_with_retry(LP8555_REG_COMMAND, reg); }
/** * Set initial power button state. */ static void set_initial_pwrbtn_state(void) { uint32_t reset_flags = system_get_reset_flags(); if (system_jumped_to_this_image() && chipset_in_state(CHIPSET_STATE_ON)) { /* * Jumped to this image while the chipset was already on, so * simply reflect the actual power button state. */ if (power_button_is_pressed()) { CPRINTS("PB init-jumped-held"); set_pwrbtn_to_pch(0); } else { CPRINTS("PB init-jumped"); } } else if ((reset_flags & RESET_FLAG_AP_OFF) || (keyboard_scan_get_boot_key() == BOOT_KEY_DOWN_ARROW)) { /* * Reset triggered by keyboard-controlled reset, and down-arrow * was held down. Or reset flags request AP off. * * Leave the main processor off. This is a fail-safe * combination for debugging failures booting the main * processor. * * Don't let the PCH see that the power button was pressed. * Otherwise, it might power on. */ CPRINTS("PB init-off"); power_button_pch_release(); } else { /* * All other EC reset conditions power on the main processor so * it can verify the EC. */ #ifdef CONFIG_BRINGUP CPRINTS("PB idle"); pwrbtn_state = PWRBTN_STATE_IDLE; #else CPRINTS("PB init-on"); pwrbtn_state = PWRBTN_STATE_INIT_ON; #endif } }
static void set_pwrbtn_to_pch(int high) { /* * If the battery is discharging and low enough we'd shut down the * system, don't press the power button. Also, don't press the * power button if the battery is charging but the battery level * is too low. */ #ifdef CONFIG_CHARGER if (chipset_in_state(CHIPSET_STATE_ANY_OFF) && !high && (charge_want_shutdown() || charge_prevent_power_on())) { CPRINTS("PB PCH pwrbtn ignored due to battery level"); high = 1; } #endif CPRINTS("PB PCH pwrbtn=%s", high ? "HIGH" : "LOW"); gpio_set_level(GPIO_PCH_PWRBTN_L, high); }
/** * Prevent battery from going into deep discharge state */ static void low_battery_shutdown(struct charge_state_context *ctx) { if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { /* AP is off, so shut down the EC now */ CPRINTS("charge force EC hibernate due to low battery"); system_hibernate(0, 0); } else if (!ctx->shutdown_warning_time.val) { /* Warn AP battery level is so low we'll shut down */ CPRINTS("charge warn shutdown due to low battery"); ctx->shutdown_warning_time = get_time(); host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN); } else if (get_time().val > ctx->shutdown_warning_time.val + LOW_BATTERY_SHUTDOWN_TIMEOUT_US) { /* Timeout waiting for AP to shut down, so kill it */ CPRINTS("charge force shutdown due to low battery"); chipset_force_shutdown(); } }
/** * Discharge state handler * * - detect ac status * - new state: INIT */ static enum charge_state state_discharge(struct charge_state_context *ctx) { struct batt_params *batt = &ctx->curr.batt; int8_t bat_temp_c = DECI_KELVIN_TO_CELSIUS(batt->temperature); if (ctx->curr.ac) return PWR_STATE_REINIT; if (ctx->curr.error) return PWR_STATE_ERROR; /* Handle overtemp in discharging state by powering off host */ if ((bat_temp_c >= ctx->battery->discharging_max_c || bat_temp_c < ctx->battery->discharging_min_c) && chipset_in_state(CHIPSET_STATE_ON)) { CPRINTS("charge force shutdown due to battery temp"); chipset_force_shutdown(); host_set_single_event(EC_HOST_EVENT_BATTERY_SHUTDOWN); } return PWR_STATE_UNCHANGE; }
void mkbp_send_event(uint8_t event_type) { set_event(event_type); #ifdef CONFIG_MKBP_WAKEUP_MASK /* checking the event if AP suspended */ if (chipset_in_state(CHIPSET_STATE_SUSPEND)) { uint32_t events; events = *(uint32_t *)host_get_memmap(EC_MEMMAP_HOST_EVENTS); /* * interrupt the AP if it is a wakeup event * which is defined in the white list. */ if ((events & CONFIG_MKBP_WAKEUP_MASK) || (event_type == EC_MKBP_EVENT_KEY_MATRIX)) set_host_interrupt(1); return; } #endif set_host_interrupt(1); }
static void oak_led_set_battery(int board_version) { static int battery_second; battery_second++; switch(board_version) { case OAK_REV3: case OAK_REV4: /* * For Rev3 and Rev4 revision: * BAT LED behavior: * - Fully charged / idle: Green ON * - Charging: Amber ON (BAT_LED_RED && BAT_LED_GREEN) * - Battery discharging capacity<10%, red blink * - Battery error: Red ON */ switch (charge_get_state()) { case PWR_STATE_CHARGE: bat_led_set(BAT_LED_AMBER, 1); break; case PWR_STATE_CHARGE_NEAR_FULL: bat_led_set(BAT_LED_GREEN, 1); bat_led_set(BAT_LED_RED, 0); break; case PWR_STATE_DISCHARGE: bat_led_set(BAT_LED_GREEN, 0); if (charge_get_percent() < 3) bat_led_set(BAT_LED_RED, (battery_second & 1) ? 0 : 1); else if (charge_get_percent() < 10) bat_led_set(BAT_LED_RED, (battery_second & 3) ? 0 : 1); else bat_led_set(BAT_LED_RED, 0); break; case PWR_STATE_ERROR: bat_led_set(BAT_LED_RED, 1); break; case PWR_STATE_IDLE: /* Ext. power connected in IDLE. */ bat_led_set(BAT_LED_GREEN, 1); bat_led_set(BAT_LED_RED, 0); break; default: /* Other states don't alter LED behavior */ break; } break; /* End of case OAK_REV3 & OAK_REV4 */ default: /* * Put power control here since we are using the "battery" LED. * This allows LED autocontrol to be turned off by cmd during factory test. * * PWR LED behavior: * Power on: Green * Suspend: Green in breeze mode ( 1 sec on/ 3 sec off) * Power off: OFF */ if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) bat_led_set(BAT_LED_GREEN, 0); else if (chipset_in_state(CHIPSET_STATE_ON)) bat_led_set(BAT_LED_GREEN, 1); else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) { int cycle_time = 4; /* Oak rev5 with GlaDOS ID has a extremely power * comsuming LED. Increase LED blink cycle time to reduce * S3 power comsuption. */ if (board_version >= OAK_REV5) cycle_time = 10; bat_led_set(BAT_LED_GREEN, (battery_second % cycle_time) ? 0 : 1); } /* BAT LED behavior: * Fully charged / idle: Off * Under charging: Orange * Bat. low (10%): Orange in breeze mode (1s on, 3s off) * Bat. critical low (less than 3%) or abnormal battery * situation: Orange in blinking mode (1s on, 1s off) * Using battery or not connected to AC power: OFF */ switch (charge_get_state()) { case PWR_STATE_CHARGE: bat_led_set(BAT_LED_ORANGE, 1); break; case PWR_STATE_CHARGE_NEAR_FULL: bat_led_set(BAT_LED_ORANGE, 1); break; case PWR_STATE_DISCHARGE: if (charge_get_percent() < 3) bat_led_set(BAT_LED_ORANGE, (battery_second & 1) ? 0 : 1); else if (charge_get_percent() < 10) bat_led_set(BAT_LED_ORANGE, (battery_second & 3) ? 0 : 1); else bat_led_set(BAT_LED_ORANGE, 0); break; case PWR_STATE_ERROR: bat_led_set(BAT_LED_ORANGE, (battery_second & 1) ? 0 : 1); break; case PWR_STATE_IDLE: /* Ext. power connected in IDLE. */ bat_led_set(BAT_LED_ORANGE, 0); break; default: /* Other states don't alter LED behavior */ break; } break; /* End of default */ } }
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 chiset 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; }
/* 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); } }
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 guage IC. Charging loop will enable the * charger and turn on trickle charging. For safty 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; } } }