From b004e7e397577d95404fd31aec68a5c54904a48c Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 29 Jun 2022 00:22:49 +1000 Subject: [PATCH] rp2/modmachine: Implement lightsleep() with optional sleep period. This gets basic machine.lightsleep([n]) behaviour working on the rp2 port. It supports: - Calling lightsleep without a specified period, in which case it uses xosc dormant mode. There's currently no way to wake it up from this state, unless you write to raw registers to enable a GPIO wake up source. - Calling lightsleep with a period n in milliseconds. This period must be less than about 72 minutes and uses timer alarm3 to wake it up. The RTC continues to run during lightsleep, but other peripherals have their clock turned off during the sleep. It doesn't yet support longer periods than 72 minutes, or waking up from GPIO IRQ. Measured current consumption from the USB port on a PICO board is about 1.5mA when doing machine.lightsleep(5000), and about 0.9mA when doing machine.lightsleep(). Addresses issue #8770. Signed-off-by: Damien George --- ports/rp2/CMakeLists.txt | 2 + ports/rp2/modmachine.c | 80 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 0088ba2c6d..9f71f65840 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -150,6 +150,7 @@ set(PICO_SDK_COMPONENTS hardware_i2c hardware_irq hardware_pio + hardware_pll hardware_pwm hardware_regs hardware_rtc @@ -159,6 +160,7 @@ set(PICO_SDK_COMPONENTS hardware_timer hardware_uart hardware_watchdog + hardware_xosc pico_base_headers pico_binary_info pico_bootrom diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index b988afdf30..3c8922c417 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -38,7 +38,12 @@ #include "modmachine.h" #include "uart.h" #include "hardware/clocks.h" +#include "hardware/pll.h" +#include "hardware/structs/rosc.h" +#include "hardware/structs/scb.h" +#include "hardware/structs/syscfg.h" #include "hardware/watchdog.h" +#include "hardware/xosc.h" #include "pico/bootrom.h" #include "pico/stdlib.h" #include "pico/unique_id.h" @@ -83,6 +88,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); NORETURN mp_obj_t machine_bootloader(size_t n_args, const mp_obj_t *args) { MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args); + rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; reset_usb_boot(0, 0); for (;;) { } @@ -113,13 +119,77 @@ STATIC mp_obj_t machine_idle(void) { STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { - if (n_args == 0) { - for (;;) { - MICROPY_EVENT_POLL_HOOK + mp_int_t delay_ms = 0; + bool use_timer_alarm = false; + + if (n_args == 1) { + delay_ms = mp_obj_get_int(args[0]); + if (delay_ms <= 1) { + // Sleep is too small, just use standard delay. + mp_hal_delay_ms(delay_ms); + return mp_const_none; + } + use_timer_alarm = delay_ms < (1ULL << 32) / 1000; + if (use_timer_alarm) { + // Use timer alarm to wake. + } else { + // TODO: Use RTC alarm to wake. + mp_raise_ValueError(MP_ERROR_TEXT("sleep too long")); } - } else { - mp_hal_delay_ms(mp_obj_get_int(args[0])); } + + const uint32_t xosc_hz = XOSC_MHZ * 1000000; + + // Disable USB and ADC clocks. + clock_stop(clk_usb); + clock_stop(clk_adc); + + // CLK_REF = XOSC + clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, xosc_hz, xosc_hz); + + // CLK_SYS = CLK_REF + clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 0, xosc_hz, xosc_hz); + + // CLK_RTC = XOSC / 256 + clock_configure(clk_rtc, 0, CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC, xosc_hz, xosc_hz / 256); + + // CLK_PERI = CLK_SYS + clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, xosc_hz, xosc_hz); + + // Disable PLLs. + pll_deinit(pll_sys); + pll_deinit(pll_usb); + + // Disable ROSC. + rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB; + + if (n_args == 0) { + xosc_dormant(); + } else { + uint32_t sleep_en0 = clocks_hw->sleep_en0; + uint32_t sleep_en1 = clocks_hw->sleep_en1; + clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; + if (use_timer_alarm) { + // Use timer alarm to wake. + clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS; + timer_hw->alarm[3] = timer_hw->timerawl + delay_ms * 1000; + } else { + // TODO: Use RTC alarm to wake. + clocks_hw->sleep_en1 = 0; + } + scb_hw->scr |= M0PLUS_SCR_SLEEPDEEP_BITS; + __wfi(); + scb_hw->scr &= ~M0PLUS_SCR_SLEEPDEEP_BITS; + clocks_hw->sleep_en0 = sleep_en0; + clocks_hw->sleep_en1 = sleep_en1; + } + + // Enable ROSC. + rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB; + + // Bring back all clocks. + clocks_init(); + return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj, 0, 1, machine_lightsleep);