static int test_low_battery(void) { test_setup(1); ccprintf("[CHARGING TEST] Low battery with AC\n"); sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2); wait_charging_state(); mock_chipset_state = CHIPSET_STATE_SOFT_OFF; hook_notify(HOOK_CHIPSET_SHUTDOWN); TEST_ASSERT(!is_hibernated); ccprintf("[CHARGING TEST] Low battery shutdown S0->S5\n"); mock_chipset_state = CHIPSET_STATE_ON; hook_notify(HOOK_CHIPSET_PRE_INIT); hook_notify(HOOK_CHIPSET_STARTUP); gpio_set_level(GPIO_AC_PRESENT, 0); is_hibernated = 0; sb_write(SB_CURRENT, -1000); sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2); wait_charging_state(); mock_chipset_state = CHIPSET_STATE_SOFT_OFF; hook_notify(HOOK_CHIPSET_SHUTDOWN); wait_charging_state(); TEST_ASSERT(is_hibernated); ccprintf("[CHARGING TEST] Low battery shutdown S5\n"); is_hibernated = 0; sb_write(SB_RELATIVE_STATE_OF_CHARGE, 10); wait_charging_state(); sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2); wait_charging_state(); TEST_ASSERT(is_hibernated); ccprintf("[CHARGING TEST] Low battery AP shutdown\n"); is_shutdown = 0; mock_chipset_state = CHIPSET_STATE_ON; sb_write(SB_RELATIVE_STATE_OF_CHARGE, 10); gpio_set_level(GPIO_AC_PRESENT, 1); sb_write(SB_CURRENT, 1000); wait_charging_state(); gpio_set_level(GPIO_AC_PRESENT, 0); sb_write(SB_CURRENT, -1000); sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2); wait_charging_state(); usleep(32 * SECOND); wait_charging_state(); TEST_ASSERT(is_shutdown); return EC_SUCCESS; }
/** * Power off the AP */ static void power_off(void) { unsigned int power_off_timeout = 100; /* ms */ /* Call hooks before we drop power rails */ hook_notify(HOOK_CHIPSET_SHUTDOWN); /* switch off all rails */ chipset_turn_off_power_rails(); /* Change SUSPEND_L and EC_INT pin to high-Z to reduce power draw. */ gpio_set_flags(GPIO_SUSPEND_L, GPIO_INPUT); gpio_set_flags(GPIO_EC_INT_L, GPIO_INPUT); /* Wait till we actually turn off to not mess up the state machine. */ while (power_get_signals() & IN_POWER_GOOD) { msleep(1); power_off_timeout--; ASSERT(power_off_timeout); } lid_opened = 0; enable_sleep(SLEEP_MASK_AP_RUN); powerled_set_state(POWERLED_STATE_OFF); CPRINTS("power shutdown complete"); }
/** * Handle debounced power button changing state. */ static void power_button_change_deferred(void) { const int new_pressed = raw_power_button_pressed(); /* Re-enable keyboard scanning if power button is no longer pressed */ if (!new_pressed) keyboard_scan_enable(1, KB_SCAN_DISABLE_POWER_BUTTON); /* If power button hasn't changed state, nothing to do */ if (new_pressed == debounced_power_pressed) { power_button_is_stable = 1; return; } debounced_power_pressed = new_pressed; power_button_is_stable = 1; CPRINTS("power button %s", new_pressed ? "pressed" : "released"); /* Call hooks */ hook_notify(HOOK_POWER_BUTTON_CHANGE); /* Notify host if power button has been pressed */ if (new_pressed) host_set_single_event(EC_HOST_EVENT_POWER_BUTTON); }
static int lid_test(void) { lid_open = 0; hook_notify(HOOK_LID_CHANGE); mock_key(1, 1, 1); TEST_ASSERT(expect_no_keychange() == EC_SUCCESS); mock_key(1, 1, 0); TEST_ASSERT(expect_no_keychange() == EC_SUCCESS); lid_open = 1; hook_notify(HOOK_LID_CHANGE); mock_key(1, 1, 1); TEST_ASSERT(expect_keychange() == EC_SUCCESS); mock_key(1, 1, 0); TEST_ASSERT(expect_keychange() == EC_SUCCESS); return EC_SUCCESS; }
/** * Jump to what we hope is the init address of an image. * * This function does not return. * * @param init_addr Init address of target image */ static void jump_to_image(uintptr_t init_addr) { void (*resetvec)(void) = (void(*)(void))init_addr; /* * Jumping to any image asserts the signal to the Silego chip that that * EC is not in read-only firmware. (This is not technically true if * jumping from RO -> RO, but that's not a meaningful use case...). * * Pulse the signal long enough to set the latch in the Silego, then * drop it again so we don't leak power through the pulldown in the * Silego. */ gpio_set_level(GPIO_ENTERING_RW, 1); usleep(MSEC); gpio_set_level(GPIO_ENTERING_RW, 0); #ifdef CONFIG_USB_POWER_DELIVERY /* * Notify USB PD module that we are about to sysjump and give it time * to do what it needs. */ pd_prepare_sysjump(); usleep(5*MSEC); #endif /* Flush UART output unless the UART hasn't been initialized yet */ if (uart_init_done()) uart_flush_output(); /* Disable interrupts before jump */ interrupt_disable(); #ifdef CONFIG_DMA /* Disable all DMA channels to avoid memory corruption */ dma_disable_all(); #endif /* CONFIG_DMA */ /* Fill in preserved data between jumps */ jdata->reserved0 = 0; jdata->magic = JUMP_DATA_MAGIC; jdata->version = JUMP_DATA_VERSION; jdata->reset_flags = reset_flags; jdata->jump_tag_total = 0; /* Reset tags */ jdata->struct_size = sizeof(struct jump_data); /* Call other hooks; these may add tags */ hook_notify(HOOK_SYSJUMP); /* Jump to the reset vector */ resetvec(); }
static void run_test_step3(void) { lid_open = 1; hook_notify(HOOK_LID_CHANGE); test_reset(); RUN_TEST(test_check_boot_down); if (test_get_error_count()) test_reboot_to_next_step(TEST_STATE_FAILED); else test_reboot_to_next_step(TEST_STATE_PASSED); }
/** * Jump to what we hope is the init address of an image. * * This function does not return. * * @param init_addr Init address of target image */ static void jump_to_image(uintptr_t init_addr) { void (*resetvec)(void) = (void(*)(void))init_addr; /* * Jumping to any image asserts the signal to the Silego chip that that * EC is not in read-only firmware. (This is not technically true if * jumping from RO -> RO, but that's not a meaningful use case...). * * Pulse the signal long enough to set the latch in the Silego, then * drop it again so we don't leak power through the pulldown in the * Silego. */ gpio_set_level(GPIO_ENTERING_RW, 1); usleep(MSEC); gpio_set_level(GPIO_ENTERING_RW, 0); #ifdef CONFIG_USB_POWER_DELIVERY /* * On sysjump, we are most definitely going to drop pings (if any) * and lose all of our PD state. Instead of trying to remember all * the states and deal with on-going transmission, let's send soft * reset here so that the communication starts over without dropping * power. */ pd_soft_reset(); usleep(5*MSEC); #endif /* Flush UART output unless the UART hasn't been initialized yet */ if (uart_init_done()) uart_flush_output(); /* Disable interrupts before jump */ interrupt_disable(); /* Fill in preserved data between jumps */ jdata->reserved0 = 0; jdata->magic = JUMP_DATA_MAGIC; jdata->version = JUMP_DATA_VERSION; jdata->reset_flags = reset_flags; jdata->jump_tag_total = 0; /* Reset tags */ jdata->struct_size = sizeof(struct jump_data); /* Call other hooks; these may add tags */ hook_notify(HOOK_SYSJUMP); /* Jump to the reset vector */ resetvec(); }
/** * Deferred function to handle external power change */ static void extpower_deferred(void) { int extpower_presence = gpio_get_level(GPIO_AC_PRESENT); if (extpower_presence == debounced_extpower_presence) return; debounced_extpower_presence = extpower_presence; hook_notify(HOOK_AC_CHANGE); /* Forward notification to host */ if (extpower_presence) host_set_single_event(EC_HOST_EVENT_AC_CONNECTED); else host_set_single_event(EC_HOST_EVENT_AC_DISCONNECTED); }
/** * Power off the AP */ static void power_off(void) { /* Call hooks before we drop power rails */ hook_notify(HOOK_CHIPSET_SHUTDOWN); /* switch off all rails */ chipset_turn_off_power_rails(); /* Change SUSPEND_L and EC_INT pin to high-Z to reduce power draw. */ gpio_set_flags(GPIO_SUSPEND_L, GPIO_INPUT); gpio_set_flags(GPIO_EC_INT, GPIO_INPUT); lid_opened = 0; enable_sleep(SLEEP_MASK_AP_RUN); powerled_set_state(POWERLED_STATE_OFF); CPRINTS("power shutdown complete"); }
/** * Jump to what we hope is the init address of an image. * * This function does not return. * * @param init_addr Init address of target image */ static void jump_to_image(uintptr_t init_addr) { void (*resetvec)(void) = (void(*)(void))init_addr; /* * Jumping to any image asserts the signal to the Silego chip that that * EC is not in read-only firmware. (This is not technically true if * jumping from RO -> RO, but that's not a meaningful use case...). * * Pulse the signal long enough to set the latch in the Silego, then * drop it again so we don't leak power through the pulldown in the * Silego. */ gpio_set_level(GPIO_ENTERING_RW, 1); usleep(MSEC); gpio_set_level(GPIO_ENTERING_RW, 0); #if defined(CONFIG_I2C) && !defined(CONFIG_I2C_SLAVE_ONLY) /* Prepare I2C module for sysjump */ i2c_prepare_sysjump(); #endif /* Flush UART output */ cflush(); /* Disable interrupts before jump */ interrupt_disable(); #ifdef CONFIG_DMA /* Disable all DMA channels to avoid memory corruption */ dma_disable_all(); #endif /* CONFIG_DMA */ /* Fill in preserved data between jumps */ jdata->reserved0 = 0; jdata->magic = JUMP_DATA_MAGIC; jdata->version = JUMP_DATA_VERSION; jdata->reset_flags = reset_flags; jdata->jump_tag_total = 0; /* Reset tags */ jdata->struct_size = sizeof(struct jump_data); /* Call other hooks; these may add tags */ hook_notify(HOOK_SYSJUMP); /* Jump to the reset vector */ resetvec(); }
static void run_test_step1(void) { lid_open = 1; hook_notify(HOOK_LID_CHANGE); test_reset(); RUN_TEST(deghost_test); RUN_TEST(debounce_test); RUN_TEST(simulate_key_test); #ifdef EMU_BUILD RUN_TEST(runtime_key_test); #endif #ifdef CONFIG_LID_SWITCH RUN_TEST(lid_test); #endif if (test_get_error_count()) test_reboot_to_next_step(TEST_STATE_FAILED); else test_reboot_to_next_step(TEST_STATE_STEP_2); }
/** * Jump to what we hope is the init address of an image. * * This function does not return. * * @param init_addr Init address of target image */ static void jump_to_image(uintptr_t init_addr) { void (*resetvec)(void) = (void(*)(void))init_addr; /* * Jumping to any image asserts the signal to the Silego chip that that * EC is not in read-only firmware. (This is not technically true if * jumping from RO -> RO, but that's not a meaningful use case...). * * Pulse the signal long enough to set the latch in the Silego, then * drop it again so we don't leak power through the pulldown in the * Silego. */ gpio_set_level(GPIO_ENTERING_RW, 1); usleep(MSEC); gpio_set_level(GPIO_ENTERING_RW, 0); /* Flush UART output unless the UART hasn't been initialized yet */ if (uart_init_done()) uart_flush_output(); /* Disable interrupts before jump */ interrupt_disable(); /* Fill in preserved data between jumps */ jdata->reserved0 = 0; jdata->magic = JUMP_DATA_MAGIC; jdata->version = JUMP_DATA_VERSION; jdata->reset_flags = reset_flags; jdata->jump_tag_total = 0; /* Reset tags */ jdata->struct_size = sizeof(struct jump_data); /* Call other hooks; these may add tags */ hook_notify(HOOK_SYSJUMP); /* Jump to the reset vector */ resetvec(); }
static void lpc_chipset_reset(void) { hook_notify(HOOK_CHIPSET_RESET); }
/** * Battery charging task */ void charger_task(void) { struct charge_state_context *ctx = &task_ctx; timestamp_t ts; int sleep_usec = CHARGE_POLL_PERIOD_SHORT, diff_usec, sleep_next; enum charge_state new_state; uint8_t batt_flags; while (1) { #ifdef CONFIG_SB_FIRMWARE_UPDATE if (sb_fw_update_in_progress()) { task_wait_event(CHARGE_MAX_SLEEP_USEC); continue; } #endif state_common(ctx); #ifdef CONFIG_CHARGER_TIMEOUT_HOURS if (ctx->curr.state == PWR_STATE_CHARGE && ctx->charge_state_updated_time.val + CONFIG_CHARGER_TIMEOUT_HOURS * HOUR < ctx->curr.ts.val) { CPRINTS("Charge timed out after %d hours", CONFIG_CHARGER_TIMEOUT_HOURS); charge_force_idle(1); } #endif /* CONFIG_CHARGER_TIMEOUT_HOURS */ switch (ctx->prev.state) { case PWR_STATE_INIT: case PWR_STATE_REINIT: new_state = state_init(ctx); break; case PWR_STATE_IDLE0: new_state = state_idle(ctx); /* If still idling, move from IDLE0 to IDLE */ if (new_state == PWR_STATE_UNCHANGE) new_state = PWR_STATE_IDLE; break; case PWR_STATE_IDLE: new_state = state_idle(ctx); break; case PWR_STATE_DISCHARGE: new_state = state_discharge(ctx); break; case PWR_STATE_CHARGE: new_state = state_charge(ctx); if (new_state == PWR_STATE_UNCHANGE && (ctx->curr.batt.state_of_charge >= BATTERY_LEVEL_NEAR_FULL)) { /* Almost done charging */ new_state = PWR_STATE_CHARGE_NEAR_FULL; } break; case PWR_STATE_CHARGE_NEAR_FULL: new_state = state_charge(ctx); if (new_state == PWR_STATE_UNCHANGE && (ctx->curr.batt.state_of_charge < BATTERY_LEVEL_NEAR_FULL)) { /* Battery below almost-full threshold. */ new_state = PWR_STATE_CHARGE; } break; case PWR_STATE_ERROR: new_state = state_error(ctx); break; default: CPRINTS("Charge state %d undefined", ctx->curr.state); ctx->curr.state = PWR_STATE_ERROR; new_state = PWR_STATE_ERROR; } if (state_machine_force_idle && ctx->prev.state != PWR_STATE_IDLE0 && ctx->prev.state != PWR_STATE_IDLE && ctx->prev.state != PWR_STATE_INIT && ctx->prev.state != PWR_STATE_REINIT) new_state = PWR_STATE_REINIT; if (new_state) { ctx->curr.state = new_state; CPRINTS("Charge state %s -> %s after %.6ld sec", state_name[ctx->prev.state], state_name[new_state], ctx->curr.ts.val - ctx->charge_state_updated_time.val); ctx->charge_state_updated_time = ctx->curr.ts; hook_notify(HOOK_CHARGE_STATE_CHANGE); } switch (new_state) { case PWR_STATE_IDLE0: /* * First time transitioning from init -> idle. Don't * set the flags or LED yet because we may transition * to charging on the next call and we don't want to * blink the LED green. */ sleep_usec = CHARGE_POLL_PERIOD_SHORT; break; case PWR_STATE_CHARGE_NEAR_FULL: /* * Battery is almost charged. The last few percent * take a loooong time, so fall through and look like * we're charged. This mirrors similar hacks at the * ACPI/kernel/UI level. */ case PWR_STATE_IDLE: batt_flags = *ctx->memmap_batt_flags; batt_flags &= ~EC_BATT_FLAG_CHARGING; batt_flags &= ~EC_BATT_FLAG_DISCHARGING; *ctx->memmap_batt_flags = batt_flags; /* Charge done */ sleep_usec = (new_state == PWR_STATE_IDLE ? CHARGE_POLL_PERIOD_LONG : CHARGE_POLL_PERIOD_CHARGE); break; case PWR_STATE_DISCHARGE: batt_flags = *ctx->memmap_batt_flags; batt_flags &= ~EC_BATT_FLAG_CHARGING; batt_flags |= EC_BATT_FLAG_DISCHARGING; *ctx->memmap_batt_flags = batt_flags; sleep_usec = CHARGE_POLL_PERIOD_LONG; break; case PWR_STATE_CHARGE: batt_flags = *ctx->memmap_batt_flags; batt_flags |= EC_BATT_FLAG_CHARGING; batt_flags &= ~EC_BATT_FLAG_DISCHARGING; *ctx->memmap_batt_flags = batt_flags; /* Charging */ sleep_usec = CHARGE_POLL_PERIOD_CHARGE; break; case PWR_STATE_ERROR: /* Error */ sleep_usec = CHARGE_POLL_PERIOD_CHARGE; break; case PWR_STATE_UNCHANGE: /* Don't change sleep duration */ break; default: /* Other state; poll quickly and hope it goes away */ sleep_usec = CHARGE_POLL_PERIOD_SHORT; } #ifdef CONFIG_EXTPOWER_FALCO watch_adapter_closely(ctx); sleep_usec = EXTPOWER_FALCO_POLL_PERIOD; #endif /* Show charging progress in console */ charging_progress(ctx); ts = get_time(); diff_usec = (int)(ts.val - ctx->curr.ts.val); sleep_next = sleep_usec - diff_usec; if (ctx->curr.state == PWR_STATE_DISCHARGE && chipset_in_state(CHIPSET_STATE_ANY_OFF | CHIPSET_STATE_SUSPEND)) { /* * Discharging and system is off or suspended, so no * need to poll frequently. charge_hook() will wake us * up if anything important changes. */ sleep_next = CHARGE_POLL_PERIOD_VERY_LONG - diff_usec; } else if (sleep_next < CHARGE_MIN_SLEEP_USEC) { sleep_next = CHARGE_MIN_SLEEP_USEC; } else if (sleep_next > CHARGE_MAX_SLEEP_USEC) { sleep_next = CHARGE_MAX_SLEEP_USEC; } task_wait_event(sleep_next); } }
/* Test utilities */ static int test_lid_angle(void) { uint8_t *lpc_status = host_get_memmap(EC_MEMMAP_ACC_STATUS); uint8_t sample; struct motion_sensor_t *base = &motion_sensors[0]; struct motion_sensor_t *lid = &motion_sensors[1]; /* Go to S3 state */ hook_notify(HOOK_CHIPSET_STARTUP); /* Go to S0 state */ hook_notify(HOOK_CHIPSET_RESUME); /* * Set the base accelerometer as if it were sitting flat on a desk * and set the lid to closed. */ base->xyz[X] = 0; base->xyz[Y] = 0; base->xyz[Z] = 1000; lid->xyz[X] = 0; lid->xyz[Y] = 0; lid->xyz[Z] = 1000; sample = *lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; task_wake(TASK_ID_MOTIONSENSE); while ((*lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK) == sample) msleep(5); TEST_ASSERT(motion_lid_get_angle() == 0); /* Set lid open to 90 degrees. */ lid->xyz[X] = -1000; lid->xyz[Y] = 0; lid->xyz[Z] = 0; sample = *lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; task_wake(TASK_ID_MOTIONSENSE); while ((*lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK) == sample) msleep(5); TEST_ASSERT(motion_lid_get_angle() == 90); /* Set lid open to 225. */ lid->xyz[X] = 500; lid->xyz[Y] = 0; lid->xyz[Z] = -500; sample = *lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; task_wake(TASK_ID_MOTIONSENSE); while ((*lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK) == sample) msleep(5); TEST_ASSERT(motion_lid_get_angle() == 225); /* * Align base with hinge and make sure it returns unreliable for angle. * In this test it doesn't matter what the lid acceleration vector is. */ base->xyz[X] = 0; base->xyz[Y] = 1000; base->xyz[Z] = 0; sample = *lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; task_wake(TASK_ID_MOTIONSENSE); while ((*lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK) == sample) msleep(5); TEST_ASSERT(motion_lid_get_angle() == LID_ANGLE_UNRELIABLE); /* * Use all three axes and set lid to negative base and make sure * angle is 180. */ base->xyz[X] = 500; base->xyz[Y] = 400; base->xyz[Z] = 300; lid->xyz[X] = -500; lid->xyz[Y] = -400; lid->xyz[Z] = -300; sample = *lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; task_wake(TASK_ID_MOTIONSENSE); while ((*lpc_status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK) == sample) msleep(5); TEST_ASSERT(motion_lid_get_angle() == 180); return EC_SUCCESS; }
enum power_state power_handle_state(enum power_state state) { int value; static int boot_from_g3; switch (state) { case POWER_G3: boot_from_g3 = check_for_power_on_event(); if (boot_from_g3) return POWER_G3S5; break; case POWER_G3S5: return POWER_S5; case POWER_S5: if (boot_from_g3) { value = boot_from_g3; boot_from_g3 = 0; } else { value = check_for_power_on_event(); } if (value) { CPRINTS("power on %d", value); return POWER_S5S3; } return state; case POWER_S5S3: hook_notify(HOOK_CHIPSET_PRE_INIT); power_on(); disable_sleep(SLEEP_MASK_AP_RUN); powerled_set_state(POWERLED_STATE_ON); if (power_wait_signals(IN_POWER_GOOD) == EC_SUCCESS) { CPRINTS("POWER_GOOD seen"); if (power_button_wait_for_release( DELAY_SHUTDOWN_ON_POWER_HOLD) == EC_SUCCESS) { power_button_was_pressed = 0; set_pmic_pwron(0); /* setup misc gpio for S3/S0 functionality */ gpio_set_flags(GPIO_SUSPEND_L, GPIO_INPUT | GPIO_INT_BOTH | GPIO_PULL_DOWN); gpio_set_flags(GPIO_EC_INT_L, GPIO_OUTPUT | GPIO_OUT_HIGH); /* Call hooks now that AP is running */ hook_notify(HOOK_CHIPSET_STARTUP); return POWER_S3; } else { CPRINTS("long-press button, shutdown"); power_off(); /* * Since the AP may be up already, return S0S3 * state to go through the suspend hook. */ return POWER_S0S3; } } else { CPRINTS("POWER_GOOD not seen in time"); } chipset_turn_off_power_rails(); return POWER_S5; case POWER_S3: if (!(power_get_signals() & IN_POWER_GOOD)) return POWER_S3S5; else if (!(power_get_signals() & IN_SUSPEND)) return POWER_S3S0; return state; case POWER_S3S0: powerled_set_state(POWERLED_STATE_ON); hook_notify(HOOK_CHIPSET_RESUME); return POWER_S0; case POWER_S0: value = check_for_power_off_event(); if (value) { CPRINTS("power off %d", value); power_off(); return POWER_S0S3; } else if (power_get_signals() & IN_SUSPEND) return POWER_S0S3; return state; case POWER_S0S3: if (lid_is_open()) powerled_set_state(POWERLED_STATE_SUSPEND); else powerled_set_state(POWERLED_STATE_OFF); /* Call hooks here since we don't know it prior to AP suspend */ hook_notify(HOOK_CHIPSET_SUSPEND); return POWER_S3; case POWER_S3S5: power_button_wait_for_release(-1); power_button_was_pressed = 0; return POWER_S5; case POWER_S5G3: return POWER_G3; } return state; }
enum power_state power_handle_state(enum power_state state) { /* * Pass through RSMRST asynchronously, as PCH may not react * immediately to power changes. */ int rsmrst_in = gpio_get_level(GPIO_RSMRST_L_PGOOD); int rsmrst_out = gpio_get_level(GPIO_PCH_RSMRST_L); #ifdef GLADOS_BOARD_V2 int tries = 0; #endif if (rsmrst_in != rsmrst_out) { /* * Wait at least 10ms between power signals going high * and deasserting RSMRST to PCH. */ if (rsmrst_in) msleep(10); gpio_set_level(GPIO_PCH_RSMRST_L, rsmrst_in); CPRINTS("RSMRST: %d", rsmrst_in); } switch (state) { case POWER_G3: if (forcing_shutdown) { power_button_pch_release(); forcing_shutdown = 0; } break; case POWER_S5: if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 1) return POWER_S5S3; /* Power up to next state */ break; case POWER_S3: if (!power_has_signals(IN_PGOOD_ALL_CORE)) { /* Required rail went away */ chipset_force_shutdown(); return POWER_S3S5; } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1) { /* Power up to next state */ return POWER_S3S0; } else if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 0) { /* Power down to next state */ return POWER_S3S5; } break; case POWER_S0: if (!power_has_signals(IN_PGOOD_ALL_CORE)) { chipset_force_shutdown(); return POWER_S0S3; } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 0) { /* Power down to next state */ return POWER_S0S3; } break; case POWER_G3S5: /* Call hooks to initialize PMIC */ hook_notify(HOOK_CHIPSET_PRE_INIT); if (power_wait_signals(IN_PCH_SLP_SUS_DEASSERTED)) { chipset_force_shutdown(); return POWER_G3; } #ifdef GLADOS_BOARD_V2 /* * Allow up to 1s for charger to be initialized, in case * we're trying to boot the AP with no battery. */ while (charge_prevent_power_on() && tries++ < CHARGER_INITIALIZED_TRIES) { msleep(CHARGER_INITIALIZED_DELAY_MS); } /* Return to G3 if battery level is too low */ if (charge_want_shutdown() || tries == CHARGER_INITIALIZED_TRIES) { CPRINTS("power-up inhibited"); chipset_force_shutdown(); return POWER_G3; } /* Allow AP to power on */ gpio_set_level(GPIO_PMIC_SLP_SUS_L, 1); gpio_set_level(GPIO_PCH_BATLOW_L, 1); #endif return POWER_S5; case POWER_S5S3: if (!power_has_signals(IN_PGOOD_ALL_CORE)) { /* Required rail went away */ chipset_force_shutdown(); return POWER_S5G3; } /* Enable TP + USB so that they can wake the system */ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 1); gpio_set_level(GPIO_USB1_ENABLE, 1); gpio_set_level(GPIO_USB2_ENABLE, 1); /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_STARTUP); return POWER_S3; case POWER_S3S0: if (!power_has_signals(IN_PGOOD_ALL_CORE)) { /* Required rail went away */ chipset_force_shutdown(); return POWER_S3S5; } gpio_set_level(GPIO_ENABLE_BACKLIGHT, 1); /* Enable wireless */ wireless_set_state(WIRELESS_ON); /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_RESUME); /* * Disable idle task deep sleep. This means that the low * power idle task will not go into deep sleep while in S0. */ disable_sleep(SLEEP_MASK_AP_RUN); /* * Throttle CPU if necessary. This should only be asserted * when +VCCP is powered (it is by now). */ gpio_set_level(GPIO_CPU_PROCHOT, throttle_cpu); return POWER_S0; case POWER_S0S3: /* Call hooks before we remove power rails */ hook_notify(HOOK_CHIPSET_SUSPEND); gpio_set_level(GPIO_ENABLE_BACKLIGHT, 0); /* Suspend wireless */ wireless_set_state(WIRELESS_SUSPEND); /* * Enable idle task deep sleep. Allow the low power idle task * to go into deep sleep in S3 or lower. */ enable_sleep(SLEEP_MASK_AP_RUN); return POWER_S3; case POWER_S3S5: /* Call hooks before we remove power rails */ hook_notify(HOOK_CHIPSET_SHUTDOWN); /* Disable wireless */ wireless_set_state(WIRELESS_OFF); gpio_set_level(GPIO_ENABLE_TOUCHPAD, 0); gpio_set_level(GPIO_USB1_ENABLE, 0); gpio_set_level(GPIO_USB2_ENABLE, 0); return POWER_S5G3; case POWER_S5G3: #ifdef CONFIG_G3_SLEEP gpio_set_level(GPIO_G3_SLEEP_EN, 1); #endif chipset_force_g3(); return POWER_G3; default: break; } return state; }
/* 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); } }
/** * Set which oscillator is used for the clock * * @param osc Oscillator to use */ static void clock_set_osc(enum clock_osc osc) { uint32_t tmp_acr; if (osc == current_osc) return; if (current_osc != OSC_INIT) hook_notify(HOOK_PRE_FREQ_CHANGE); switch (osc) { case OSC_HSI: /* Ensure that HSI is ON */ if (!(STM32_RCC_CR & STM32_RCC_CR_HSIRDY)) { /* Enable HSI */ STM32_RCC_CR |= STM32_RCC_CR_HSION; /* Wait for HSI to be ready */ while (!(STM32_RCC_CR & STM32_RCC_CR_HSIRDY)) ; } /* Disable LPSDSR */ STM32_PWR_CR &= ~STM32_PWR_CR_LPSDSR; /* * Set the recommended flash settings for 16MHz clock. * * The 3 bits must be programmed strictly sequentially. * Also, follow the RM to check 64-bit access and latency bit * after writing those bits to the FLASH_ACR register. */ tmp_acr = STM32_FLASH_ACR; /* Enable 64-bit access */ tmp_acr |= STM32_FLASH_ACR_ACC64; STM32_FLASH_ACR = tmp_acr; /* Check ACC64 bit == 1 */ while (!(STM32_FLASH_ACR & STM32_FLASH_ACR_ACC64)) ; /* Enable Prefetch Buffer */ tmp_acr |= STM32_FLASH_ACR_PRFTEN; STM32_FLASH_ACR = tmp_acr; /* Flash 1 wait state */ tmp_acr |= STM32_FLASH_ACR_LATENCY; STM32_FLASH_ACR = tmp_acr; /* Check LATENCY bit == 1 */ while (!(STM32_FLASH_ACR & STM32_FLASH_ACR_LATENCY)) ; /* Switch to HSI */ STM32_RCC_CFGR = STM32_RCC_CFGR_SW_HSI; /* RM says to check SWS bits to make sure HSI is the sysclock */ while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) != STM32_RCC_CFGR_SWS_HSI) ; /* Disable MSI */ STM32_RCC_CR &= ~STM32_RCC_CR_MSION; freq = HSI_CLOCK; break; case OSC_MSI: /* Switch to MSI @ 1MHz */ STM32_RCC_ICSCR = (STM32_RCC_ICSCR & ~STM32_RCC_ICSCR_MSIRANGE_MASK) | STM32_RCC_ICSCR_MSIRANGE_1MHZ; /* Ensure that MSI is ON */ if (!(STM32_RCC_CR & STM32_RCC_CR_MSIRDY)) { /* Enable MSI */ STM32_RCC_CR |= STM32_RCC_CR_MSION; /* Wait for MSI to be ready */ while (!(STM32_RCC_CR & STM32_RCC_CR_MSIRDY)) ; } /* Switch to MSI */ STM32_RCC_CFGR = STM32_RCC_CFGR_SW_MSI; /* RM says to check SWS bits to make sure MSI is the sysclock */ while ((STM32_RCC_CFGR & STM32_RCC_CFGR_SWS_MASK) != STM32_RCC_CFGR_SWS_MSI) ; /* * Set the recommended flash settings for <= 2MHz clock. * * The 3 bits must be programmed strictly sequentially. * Also, follow the RM to check 64-bit access and latency bit * after writing those bits to the FLASH_ACR register. */ tmp_acr = STM32_FLASH_ACR; /* Flash 0 wait state */ tmp_acr &= ~STM32_FLASH_ACR_LATENCY; STM32_FLASH_ACR = tmp_acr; /* Check LATENCY bit == 0 */ while (STM32_FLASH_ACR & STM32_FLASH_ACR_LATENCY) ; /* Disable prefetch Buffer */ tmp_acr &= ~STM32_FLASH_ACR_PRFTEN; STM32_FLASH_ACR = tmp_acr; /* Disable 64-bit access */ tmp_acr &= ~STM32_FLASH_ACR_ACC64; STM32_FLASH_ACR = tmp_acr; /* Check ACC64 bit == 0 */ while (STM32_FLASH_ACR & STM32_FLASH_ACR_ACC64) ; /* Disable HSI */ STM32_RCC_CR &= ~STM32_RCC_CR_HSION; /* Enable LPSDSR */ STM32_PWR_CR |= STM32_PWR_CR_LPSDSR; freq = MSI_1MHZ_CLOCK; break; default: break; } /* Notify modules of frequency change unless we're initializing */ if (current_osc != OSC_INIT) { current_osc = osc; hook_notify(HOOK_FREQ_CHANGE); } else { current_osc = osc; } }
static int test_low_battery(void) { test_setup(1); ccprintf("[CHARGING TEST] Low battery with AC and positive current\n"); sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2); sb_write(SB_CURRENT, 1000); wait_charging_state(); mock_chipset_state = CHIPSET_STATE_SOFT_OFF; hook_notify(HOOK_CHIPSET_SHUTDOWN); TEST_ASSERT(!is_hibernated); ccprintf("[CHARGING TEST] Low battery with AC and negative current\n"); sb_write(SB_CURRENT, -1000); wait_charging_state(); sleep(CONFIG_BATTERY_CRITICAL_SHUTDOWN_TIMEOUT); TEST_ASSERT(is_hibernated); ccprintf("[CHARGING TEST] Low battery shutdown S0->S5\n"); mock_chipset_state = CHIPSET_STATE_ON; hook_notify(HOOK_CHIPSET_PRE_INIT); hook_notify(HOOK_CHIPSET_STARTUP); gpio_set_level(GPIO_AC_PRESENT, 0); is_hibernated = 0; sb_write(SB_CURRENT, -1000); sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2); wait_charging_state(); mock_chipset_state = CHIPSET_STATE_SOFT_OFF; hook_notify(HOOK_CHIPSET_SHUTDOWN); wait_charging_state(); /* after a while, the EC should hibernate */ sleep(CONFIG_BATTERY_CRITICAL_SHUTDOWN_TIMEOUT); TEST_ASSERT(is_hibernated); ccprintf("[CHARGING TEST] Low battery shutdown S5\n"); is_hibernated = 0; sb_write(SB_RELATIVE_STATE_OF_CHARGE, 10); wait_charging_state(); sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2); wait_charging_state(); /* after a while, the EC should hibernate */ sleep(CONFIG_BATTERY_CRITICAL_SHUTDOWN_TIMEOUT); TEST_ASSERT(is_hibernated); ccprintf("[CHARGING TEST] Low battery AP shutdown\n"); is_shutdown = 0; mock_chipset_state = CHIPSET_STATE_ON; sb_write(SB_RELATIVE_STATE_OF_CHARGE, 10); gpio_set_level(GPIO_AC_PRESENT, 1); sb_write(SB_CURRENT, 1000); wait_charging_state(); gpio_set_level(GPIO_AC_PRESENT, 0); sb_write(SB_CURRENT, -1000); sb_write(SB_RELATIVE_STATE_OF_CHARGE, 2); wait_charging_state(); usleep(32 * SECOND); wait_charging_state(); TEST_ASSERT(is_shutdown); return EC_SUCCESS; }
enum power_state power_handle_state(enum power_state state) { switch (state) { case POWER_G3: break; case POWER_S5: if (gpio_get_level(GPIO_PCH_SLP_S5_L) == 1) return POWER_S5S3; /* Power up to next state */ break; case POWER_S3: /* Check for state transitions */ if (!power_has_signals(IN_PGOOD_S3)) { /* Required rail went away */ chipset_force_shutdown(); return POWER_S3S5; } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1) { /* Power up to next state */ return POWER_S3S0; } else if (gpio_get_level(GPIO_PCH_SLP_S5_L) == 0) { /* Power down to next state */ return POWER_S3S5; } break; case POWER_S0: if (!power_has_signals(IN_PGOOD_S0)) { /* Required rail went away */ chipset_force_shutdown(); return POWER_S0S3; } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 0) { /* Power down to next state */ return POWER_S0S3; } break; case POWER_G3S5: /* Enable 3.3V DSW */ gpio_set_level(GPIO_PP3300_DSW_EN, 1); /* * Wait 10ms after +3VALW good, since that powers VccDSW and * VccSUS. */ msleep(10); /* Enable PP5000 (5V) rail as 1.05V and 1.2V rails need 5V * rail to regulate properly. */ gpio_set_level(GPIO_PP5000_EN, 1); /* Wait for PP1050/PP1200 PGOOD to go LOW to * indicate that PP5000 is stable */ while ((power_get_signals() & IN_PGOOD_PP5000) != 0) { if (task_wait_event(SECOND) == TASK_EVENT_TIMER) { CPRINTS("timeout waiting for PP5000"); gpio_set_level(GPIO_PP5000_EN, 0); chipset_force_shutdown(); return POWER_G3; } } /* Turn on 3.3V DSW gated rail for core regulator */ gpio_set_level(GPIO_PP3300_DSW_GATED_EN, 1); /* Assert DPWROK */ gpio_set_level(GPIO_PCH_DPWROK, 1); /* Enable PP1050 rail. */ gpio_set_level(GPIO_PP1050_EN, 1); /* Wait for 1.05V to come up and CPU to notice */ if (power_wait_signals(IN_PGOOD_PP1050 | IN_PCH_SLP_SUS_DEASSERTED)) { gpio_set_level(GPIO_PP1050_EN, 0); gpio_set_level(GPIO_PP3300_DSW_GATED_EN, 0); gpio_set_level(GPIO_PP5000_EN, 0); chipset_force_shutdown(); return POWER_G3; } /* Wait 5ms for SUSCLK to stabilize */ msleep(5); /* Call hook to indicate out of G3 state */ hook_notify(HOOK_CHIPSET_PRE_INIT); return POWER_S5; case POWER_S5S3: /* Turn on power to RAM */ gpio_set_level(GPIO_PP1800_EN, 1); gpio_set_level(GPIO_PP1200_EN, 1); if (power_wait_signals(IN_PGOOD_S3)) { gpio_set_level(GPIO_PP1800_EN, 0); gpio_set_level(GPIO_PP1200_EN, 0); chipset_force_shutdown(); return POWER_S5; } /* * Take lightbar out of reset, now that +5VALW is * available and we won't leak +3VALW through the reset * line. */ gpio_set_level(GPIO_LIGHTBAR_RESET_L, 1); /* * Enable touchpad power so it can wake the system from * suspend. */ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 1); /* Turn on USB power rail. */ gpio_set_level(GPIO_PP5000_USB_EN, 1); /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_STARTUP); return POWER_S3; case POWER_S3S0: /* Wait 20ms before allowing VCCST_PGOOD to rise. */ msleep(20); /* Enable wireless. */ wireless_set_state(WIRELESS_ON); /* Make sure the touchscreen is on, too. */ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 1); /* Wait for non-core power rails good */ if (power_wait_signals(IN_PGOOD_S0)) { chipset_force_shutdown(); wireless_set_state(WIRELESS_OFF); return POWER_S3; } /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_RESUME); /* * Disable idle task deep sleep. This means that the low * power idle task will not go into deep sleep while in S0. */ disable_sleep(SLEEP_MASK_AP_RUN); /* Wait 99ms after all voltages good */ msleep(99); /* * Throttle CPU if necessary. This should only be asserted * when +VCCP is powered (it is by now). */ gpio_set_level(GPIO_CPU_PROCHOT, throttle_cpu); /* Set PCH_PWROK */ gpio_set_level(GPIO_PCH_PWROK, 1); gpio_set_level(GPIO_SYS_PWROK, 1); return POWER_S0; case POWER_S0S3: /* Call hooks before we remove power rails */ hook_notify(HOOK_CHIPSET_SUSPEND); /* Clear PCH_PWROK */ gpio_set_level(GPIO_SYS_PWROK, 0); gpio_set_level(GPIO_PCH_PWROK, 0); /* Wait 40ns */ udelay(1); /* Suspend wireless */ wireless_set_state(WIRELESS_SUSPEND); /* * Enable idle task deep sleep. Allow the low power idle task * to go into deep sleep in S3 or lower. */ enable_sleep(SLEEP_MASK_AP_RUN); /* * Deassert prochot since CPU is off and we're about to drop * +VCCP. */ gpio_set_level(GPIO_CPU_PROCHOT, 0); return POWER_S3; case POWER_S3S5: /* Call hooks before we remove power rails */ hook_notify(HOOK_CHIPSET_SHUTDOWN); /* Disable wireless */ wireless_set_state(WIRELESS_OFF); /* Disable peripheral power */ gpio_set_level(GPIO_ENABLE_TOUCHPAD, 0); gpio_set_level(GPIO_PP5000_USB_EN, 0); /* Turn off power to RAM */ gpio_set_level(GPIO_PP1800_EN, 0); gpio_set_level(GPIO_PP1200_EN, 0); /* * Put touchscreen and lightbar in reset, so we won't * leak +3VALW through the reset line to chips powered * by +5VALW. * * (Note that we're no longer powering down +5VALW due * to crosbug.com/p/16600, but to minimize side effects * of that change we'll still reset these components in * S5.) */ gpio_set_level(GPIO_TOUCHSCREEN_RESET_L, 0); gpio_set_level(GPIO_LIGHTBAR_RESET_L, 0); return pause_in_s5 ? POWER_S5 : POWER_S5G3; case POWER_S5G3: /* Deassert DPWROK */ gpio_set_level(GPIO_PCH_DPWROK, 0); /* Turn off power rails enabled in S5 */ gpio_set_level(GPIO_PP1050_EN, 0); gpio_set_level(GPIO_PP3300_DSW_GATED_EN, 0); gpio_set_level(GPIO_PP5000_EN, 0); /* Disable 3.3V DSW */ gpio_set_level(GPIO_PP3300_DSW_EN, 0); return POWER_G3; } return state; }
static enum power_state _power_handle_state(enum power_state state) { int tries = 0; switch (state) { case POWER_G3: break; case POWER_S5: if (forcing_shutdown) { power_button_pch_release(); forcing_shutdown = 0; } #ifdef CONFIG_BOARD_HAS_RTC_RESET /* Wait for S5 exit and attempt RTC reset it supported */ if (power_s5_up) return power_wait_s5_rtc_reset(); #endif if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 1) return POWER_S5S3; /* Power up to next state */ break; case POWER_S3: if (!power_has_signals(IN_PGOOD_ALL_CORE)) { /* Required rail went away */ chipset_force_shutdown(); return POWER_S3S5; } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1) { /* Power up to next state */ return POWER_S3S0; } else if (gpio_get_level(GPIO_PCH_SLP_S4_L) == 0) { /* Power down to next state */ return POWER_S3S5; } break; case POWER_S0: if (!power_has_signals(IN_PGOOD_ALL_CORE)) { chipset_force_shutdown(); return POWER_S0S3; #ifdef CONFIG_POWER_S0IX } else if ((gpio_get_level(GPIO_PCH_SLP_S0_L) == 0) && (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1)) { return POWER_S0S0ix; #endif } else if (gpio_get_level(GPIO_PCH_SLP_S3_L) == 0) { /* Power down to next state */ return POWER_S0S3; } break; #ifdef CONFIG_POWER_S0IX case POWER_S0ix: /* * TODO: add code for unexpected power loss */ if ((gpio_get_level(GPIO_PCH_SLP_S0_L) == 1) && (gpio_get_level(GPIO_PCH_SLP_S3_L) == 1)) { return POWER_S0ixS0; } break; #endif case POWER_G3S5: /* Call hooks to initialize PMIC */ hook_notify(HOOK_CHIPSET_PRE_INIT); /* * Allow up to 1s for charger to be initialized, in case * we're trying to boot the AP with no battery. */ while (charge_prevent_power_on(0) && tries++ < CHARGER_INITIALIZED_TRIES) { msleep(CHARGER_INITIALIZED_DELAY_MS); } /* Return to G3 if battery level is too low */ if (charge_want_shutdown() || tries > CHARGER_INITIALIZED_TRIES) { CPRINTS("power-up inhibited"); chipset_force_shutdown(); return POWER_G3; } if (power_wait_signals(IN_PCH_SLP_SUS_DEASSERTED)) { chipset_force_shutdown(); return POWER_G3; } power_s5_up = 1; return POWER_S5; case POWER_S5S3: if (!power_has_signals(IN_PGOOD_ALL_CORE)) { /* Required rail went away */ chipset_force_shutdown(); return POWER_S5G3; } /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_STARTUP); return POWER_S3; case POWER_S3S0: if (!power_has_signals(IN_PGOOD_ALL_CORE)) { /* Required rail went away */ chipset_force_shutdown(); return POWER_S3S5; } gpio_set_level(GPIO_ENABLE_BACKLIGHT, 1); /* Enable wireless */ wireless_set_state(WIRELESS_ON); /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_RESUME); /* * Disable idle task deep sleep. This means that the low * power idle task will not go into deep sleep while in S0. */ disable_sleep(SLEEP_MASK_AP_RUN); /* * Throttle CPU if necessary. This should only be asserted * when +VCCP is powered (it is by now). */ gpio_set_level(GPIO_CPU_PROCHOT, throttle_cpu); return POWER_S0; case POWER_S0S3: /* Call hooks before we remove power rails */ hook_notify(HOOK_CHIPSET_SUSPEND); gpio_set_level(GPIO_ENABLE_BACKLIGHT, 0); /* Suspend wireless */ wireless_set_state(WIRELESS_SUSPEND); /* * Enable idle task deep sleep. Allow the low power idle task * to go into deep sleep in S3 or lower. */ enable_sleep(SLEEP_MASK_AP_RUN); return POWER_S3; #ifdef CONFIG_POWER_S0IX case POWER_S0S0ix: /* call hooks before standby */ hook_notify(HOOK_CHIPSET_SUSPEND); lpc_enable_wake_mask_for_lid_open(); /* * Enable idle task deep sleep. Allow the low power idle task * to go into deep sleep in S0ix. */ enable_sleep(SLEEP_MASK_AP_RUN); return POWER_S0ix; case POWER_S0ixS0: lpc_disable_wake_mask_for_lid_open(); /* Call hooks now that rails are up */ hook_notify(HOOK_CHIPSET_RESUME); /* * Disable idle task deep sleep. This means that the low * power idle task will not go into deep sleep while in S0. */ disable_sleep(SLEEP_MASK_AP_RUN); return POWER_S0; #endif case POWER_S3S5: /* Call hooks before we remove power rails */ hook_notify(HOOK_CHIPSET_SHUTDOWN); /* Disable wireless */ wireless_set_state(WIRELESS_OFF); /* Always enter into S5 state. The S5 state is required to * correctly handle global resets which have a bit of delay * while the SLP_Sx_L signals are asserted then deasserted. */ power_s5_up = 0; return POWER_S5; case POWER_S5G3: chipset_force_g3(); return POWER_G3; default: break; } return state; }
/** * Set system clock oscillator * * @param osc Oscillator to use * @param pll_osc Source oscillator for PLL. Ignored if osc is not PLL. */ static void clock_set_osc(enum clock_osc osc, enum clock_osc pll_osc) { uint32_t val; if (osc == current_osc) return; if (current_osc != OSC_INIT) hook_notify(HOOK_PRE_FREQ_CHANGE); switch (osc) { case OSC_HSI: /* Ensure that HSI is ON */ clock_enable_osc(osc); /* Disable LPSDSR */ STM32_PWR_CR &= ~STM32_PWR_CR_LPSDSR; /* Switch to HSI */ clock_switch_osc(osc); /* Disable MSI */ STM32_RCC_CR &= ~STM32_RCC_CR_MSION; freq = STM32_HSI_CLOCK; break; case OSC_MSI: /* Switch to MSI @ 1MHz */ STM32_RCC_ICSCR = (STM32_RCC_ICSCR & ~STM32_RCC_ICSCR_MSIRANGE_MASK) | STM32_RCC_ICSCR_MSIRANGE_1MHZ; /* Ensure that MSI is ON */ clock_enable_osc(osc); /* Switch to MSI */ clock_switch_osc(osc); /* Disable HSI */ STM32_RCC_CR &= ~STM32_RCC_CR_HSION; /* Enable LPSDSR */ STM32_PWR_CR |= STM32_PWR_CR_LPSDSR; freq = STM32_MSI_CLOCK; break; #ifdef STM32_HSE_CLOCK case OSC_HSE: /* Ensure that HSE is stable */ clock_enable_osc(osc); /* Switch to HSE */ clock_switch_osc(osc); /* Disable other clock sources */ STM32_RCC_CR &= ~(STM32_RCC_CR_MSION | STM32_RCC_CR_HSION | STM32_RCC_CR_PLLON); freq = STM32_HSE_CLOCK; break; #endif case OSC_PLL: /* Ensure that source clock is stable */ clock_enable_osc(pll_osc); /* Configure PLLCFGR */ freq = stm32_configure_pll(pll_osc, STM32_PLLM, STM32_PLLN, STM32_PLLR); ASSERT(freq > 0); /* Adjust flash latency as instructed in TRM */ val = STM32_FLASH_ACR; val &= ~STM32_FLASH_ACR_LATENCY_MASK; /* Flash 4 wait state. TODO: Should depend on freq. */ val |= 4 << STM32_FLASH_ACR_LATENCY_SHIFT; STM32_FLASH_ACR = val; while (STM32_FLASH_ACR != val) ; /* Switch to PLL */ clock_switch_osc(osc); /* TODO: Disable other sources */ break; default: break; } /* Notify modules of frequency change unless we're initializing */ if (current_osc != OSC_INIT) { current_osc = osc; hook_notify(HOOK_FREQ_CHANGE); } else { current_osc = osc; } }