Deep sleep after shutdown

This commit is contained in:
Andrew D'Angelo 2024-04-06 13:51:49 -05:00
parent 10b344b2a6
commit 702c7f6d3a
5 changed files with 168 additions and 17 deletions

View File

@ -68,6 +68,9 @@ static struct hold_key rightshift_hold_key;
static struct hold_key alt_hold_key;
static struct hold_key sym_hold_key;
// Re-enter dormant status until Pi booted again
static uint8_t reenter_dormancy = false;
static bool transition_hold_key_state(struct hold_key* hold_key, bool const pressed)
{
uint key_held_for;
@ -146,13 +149,29 @@ static void handle_power_key_event(bool pressed)
}
// Normal press / release sends KEY_STOP
if ((power_hold_key.state == KEY_STATE_PRESSED)
|| (power_hold_key.state == KEY_STATE_RELEASED)) {
if (power_hold_key.state == KEY_STATE_PRESSED) {
keyboard_inject_event(KEY_STOP, power_hold_key.state);
} else if (power_hold_key.state == KEY_STATE_RELEASED) {
// Power button events will wake out of dormancy
// Dormancy should be retriggered until power key
// is held long enough to rewake Pi
if (reenter_dormancy) {
dormant_until_power_key();
// Send power key event
} else {
keyboard_inject_event(KEY_STOP, power_hold_key.state);
}
// Short press while driver unloaded powers Pi on
} if (power_hold_key.state == KEY_STATE_HOLD) {
// Clear dormancy flag
reenter_dormancy = 0;
// Turn Pi back on
if (reg_get_value(REG_ID_DRIVER_STATE) == 0) {
pi_cancel_power_alarms();
pi_power_on(POWER_ON_BUTTON);
@ -169,11 +188,11 @@ static void handle_power_key_event(bool pressed)
// If driver loaded, send power off
if (reg_get_value(REG_ID_DRIVER_STATE) > 0) {
// Schedule power off
// Schedule power off with dormancy
uint32_t shutdown_grace_ms = MAX(
reg_get_value(REG_ID_SHUTDOWN_GRACE) * 1000,
MINIMUM_SHUTDOWN_GRACE_MS);
pi_schedule_power_off(shutdown_grace_ms);
pi_schedule_power_off_dormant(shutdown_grace_ms, &reenter_dormancy);
}
}
}

View File

@ -4,6 +4,10 @@
#include <hardware/rtc.h>
#include "hardware/clocks.h"
#include "hardware/rosc.h"
#include "hardware/structs/scb.h"
#include "backlight.h"
#include "debug.h"
#include "gpioexp.h"
@ -12,9 +16,12 @@
#include "puppet_i2c.h"
#include "reg.h"
#include "touchpad.h"
#include "usb.h"
#include "pi.h"
// https://github.com/micropython/micropython/blob/5114f2c1ea7c05fc7ab920299967595cfc5307de/ports/rp2/modmachine.c#L179
// https://github.com/raspberrypi/pico-extras/issues/41
#include "pico/sleep.h"
// since the SDK doesn't support per-GPIO irq, we use this global irq and forward it
static void gpio_irq(uint gpio, uint32_t events)
{
@ -26,10 +33,7 @@ static void gpio_irq(uint gpio, uint32_t events)
// TODO: Microphone
int main(void)
{
// The here order is important because it determines callback call order
#ifdef HAVE_USB
usb_init();
#endif
// This order is important because it determines callback call order
#ifndef NDEBUG
debug_init();
@ -55,8 +59,11 @@ int main(void)
gpio_set_irq_enabled_with_callback(0xFF, 0, true, &gpio_irq);
led_init();
pi_power_init();
dormant_until_power_key();
pi_power_on(POWER_ON_FW_INIT);
#ifndef NDEBUG

131
app/pi.c
View File

@ -6,7 +6,12 @@
#include "hardware/adc.h"
#include <hardware/pwm.h>
#include "hardware/clocks.h"
#include "hardware/rosc.h"
#include "hardware/structs/scb.h"
#include <pico/stdlib.h>
#include <pico/sleep.h>
#define LED_FLASH_CYCLE_MS 3000
#define LED_FLASH_ON_MS 200
@ -91,18 +96,24 @@ void pi_schedule_power_on(uint32_t ms)
g_power_on_alarm = add_alarm_in_ms(ms, pi_power_on_alarm_callback, NULL, true);
}
static int64_t pi_power_off_alarm_callback(alarm_id_t _, void* __)
static int64_t pi_power_off_alarm_callback(alarm_id_t _, void* u8_ptr_dormant)
{
uint8_t *dormant_flag = (uint8_t*)u8_ptr_dormant;
if (g_power_off_alarm < 0) {
return 0;
}
pi_power_off();
if (dormant_flag != NULL) {
*dormant_flag = 1;
dormant_until_power_key();
}
return 0;
}
void pi_schedule_power_off(uint32_t ms)
static void pi_schedule_power_off(uint32_t ms, uint8_t* dormant_flag)
{
// Cancel existing alarm if scheduled
if (g_power_off_alarm >= 0) {
@ -111,7 +122,18 @@ void pi_schedule_power_off(uint32_t ms)
}
// Schedule new alarm
g_power_off_alarm = add_alarm_in_ms(ms, pi_power_off_alarm_callback, NULL, true);
g_power_off_alarm = add_alarm_in_ms(ms, pi_power_off_alarm_callback,
(void*)dormant_flag, true);
}
void pi_schedule_power_off_live(uint32_t ms)
{
pi_schedule_power_off(ms, NULL);
}
void pi_schedule_power_off_dormant(uint32_t ms, uint8_t* dormant_flag)
{
pi_schedule_power_off(ms, dormant_flag);
}
void pi_cancel_power_alarms()
@ -170,7 +192,7 @@ void led_init(void)
// Default off
g_led_state.setting = LED_SET_OFF;
g_led_flash_state.setting = LED_SET_OFF;
led_sync(false, 0, 0, 0);
led_sync(true, 0, 0, 0);
}
static int64_t pi_led_flash_alarm_callback(alarm_id_t _, void* __)
@ -260,3 +282,104 @@ void led_set(struct led_state const* state)
led_sync((state->setting == LED_SET_ON), state->r, state->g, state->b);
}
}
struct sleep_state
{
uint8_t keyboard_backlight;
struct led_state led_state;
uint scb_orig, clock0_orig, clock1_orig;
};
static void sleep_prepare(struct sleep_state* ss)
{
// Save backlight and LED state
ss->keyboard_backlight = reg_get_value(REG_ID_BKL);
ss->led_state = g_led_state;
// Save existing clock states
ss->scb_orig = scb_hw->scr;
ss->clock0_orig = clocks_hw->sleep_en0;
ss->clock1_orig = clocks_hw->sleep_en1;
// Clear LED and backlight
reg_set_value(REG_ID_BKL, 0);
led_sync(true, 0, 0, 0);
}
static void sleep_resume(struct sleep_state const* ss)
{
// Recover from sleep
rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS);
scb_hw->scr = ss->scb_orig;
clocks_hw->sleep_en0 = ss->clock0_orig;
clocks_hw->sleep_en1 = ss->clock1_orig;
clocks_init();
// Restore LED and keyboard states
led_set(&ss->led_state);
reg_set_value(REG_ID_BKL, ss->keyboard_backlight);
}
void dormant_until_power_key()
{
datetime_t t;
struct sleep_state ss;
// Invalidate RTC
t.year = 1970;
t.month = 1;
t.day = 1;
t.dotw = 4;
t.hour = 0;
t.min = 0;
t.sec = 0;
// Save clocks, LED, backlight
sleep_prepare(&ss);
// Sleep until power key is pressed
sleep_run_from_xosc();
sleep_goto_dormant_until_pin(4, 0, 0);
// Restore clocks, LED, backlight
sleep_resume(&ss);
}
static void sleep_callback(void)
{}
void dormant_seconds(int seconds)
{
struct sleep_state ss;
datetime_t t;
// Save clocks, LED, backlight
sleep_prepare(&ss);
// Get datetime offset
rtc_get_datetime(&t);
t.sec += seconds;
while (t.sec >= 60) {
t.sec -= 60;
t.min += 1;
}
while (t.min >= 60) {
t.min -= 60;
t.hour += 1;
}
// May not work correctly around midnight...
while (t.hour >= 24) {
t.hour -= 24;
t.day += 1;
t.dotw = (t.dotw + 1) & 7;
}
sleep_run_from_xosc();
sleep_goto_sleep_until(&t, sleep_callback);
// Advance RTC
rtc_set_datetime(&t);
// Restore clocks, LED, backlight
sleep_resume(&ss);
}

View File

@ -17,7 +17,8 @@ void pi_power_on(enum power_on_reason reason);
void pi_power_off(void);
void pi_schedule_power_on(uint32_t ms);
void pi_schedule_power_off(uint32_t ms);
void pi_schedule_power_off_live(uint32_t ms);
void pi_schedule_power_off_dormant(uint32_t ms, uint8_t* dormant_flag);
void pi_cancel_power_alarms();
enum led_setting
@ -37,3 +38,4 @@ struct led_state
void led_init(void);
void led_set(struct led_state const* state);
void dormant_until_power_key();

View File

@ -220,7 +220,7 @@ void reg_process_packet(uint8_t in_reg, uint8_t in_data, uint8_t *out_buffer, ui
keyboard_inject_power_key();
// Power off with grace time to give Pi time to shut down
pi_schedule_power_off(shutdown_grace_ms);
pi_schedule_power_off_live(shutdown_grace_ms);
// Schedule power on
pi_schedule_power_on(rewake_ms);
@ -276,7 +276,7 @@ void reg_process_packet(uint8_t in_reg, uint8_t in_data, uint8_t *out_buffer, ui
uint32_t shutdown_grace_ms = MAX(
reg_get_value(REG_ID_SHUTDOWN_GRACE) * 1000,
MINIMUM_SHUTDOWN_GRACE_MS);
pi_schedule_power_off(shutdown_grace_ms);
pi_schedule_power_off_live(shutdown_grace_ms);
add_alarm_in_ms(shutdown_grace_ms + 10,
update_commit_alarm_callback, NULL, true);
}
@ -375,7 +375,7 @@ void reg_clear_bit(enum reg_id reg, uint8_t bit)
void reg_init(void)
{
reg_set_value(REG_ID_CFG, CFG_OVERFLOW_INT | CFG_KEY_INT | CFG_USE_MODS);
reg_set_value(REG_ID_BKL, 255);
reg_set_value(REG_ID_BKL, 128);
reg_set_value(REG_ID_DEB, 10);
reg_set_value(REG_ID_FRQ, 10); // ms
reg_set_value(REG_ID_BK2, 255);