diff --git a/src/rp2_common/hardware_rosc/include/hardware/rosc.h b/src/rp2_common/hardware_rosc/include/hardware/rosc.h index 2720f0b..ac43a6b 100644 --- a/src/rp2_common/hardware_rosc/include/hardware/rosc.h +++ b/src/rp2_common/hardware_rosc/include/hardware/rosc.h @@ -83,6 +83,8 @@ inline static void rosc_write(io_rw_32 *addr, uint32_t value) { assert(rosc_write_okay()); }; +void rosc_enable(void); + #ifdef __cplusplus } #endif diff --git a/src/rp2_common/hardware_rosc/rosc.c b/src/rp2_common/hardware_rosc/rosc.c index 69b6012..f1ece7e 100644 --- a/src/rp2_common/hardware_rosc/rosc.c +++ b/src/rp2_common/hardware_rosc/rosc.c @@ -58,4 +58,12 @@ void rosc_set_dormant(void) { rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT); // Wait for it to become stable once woken up while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); -} \ No newline at end of file +} + +void rosc_enable(void) { + //Re-enable the rosc + rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS); + + //Wait for it to become stable once restarted + while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); +} diff --git a/src/rp2_common/pico_sleep/CMakeLists.txt b/src/rp2_common/pico_sleep/CMakeLists.txt old mode 100644 new mode 100755 index bb8ea07..a0eb4e6 --- a/src/rp2_common/pico_sleep/CMakeLists.txt +++ b/src/rp2_common/pico_sleep/CMakeLists.txt @@ -1,5 +1,10 @@ pico_simple_hardware_target(sleep) -target_link_libraries(hardware_sleep INTERFACE +pico_mirrored_target_link_libraries(hardware_sleep INTERFACE hardware_clocks hardware_rosc - hardware_rtc) \ No newline at end of file + hardware_irq + pico_aon_timer + ) +target_include_directories(hardware_sleep INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/include + ) diff --git a/src/rp2_common/pico_sleep/include/pico/sleep.h b/src/rp2_common/pico_sleep/include/pico/sleep.h old mode 100644 new mode 100755 index b97a231..e061182 --- a/src/rp2_common/pico_sleep/include/pico/sleep.h +++ b/src/rp2_common/pico_sleep/include/pico/sleep.h @@ -8,7 +8,9 @@ #define _PICO_SLEEP_H_ #include "pico.h" -#include "hardware/rtc.h" +#include "hardware/rosc.h" + +#include "pico/aon_timer.h" #ifdef __cplusplus extern "C" { @@ -33,7 +35,8 @@ extern "C" { typedef enum { DORMANT_SOURCE_NONE, DORMANT_SOURCE_XOSC, - DORMANT_SOURCE_ROSC + DORMANT_SOURCE_ROSC, + DORMANT_SOURCE_LPOSC, // rp2350 only } dormant_source_t; /*! \brief Set all clock sources to the the dormant clock source to prepare for sleep. @@ -50,6 +53,12 @@ static inline void sleep_run_from_xosc(void) { sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC); } +#if !PICO_RP2040 +static inline void sleep_run_from_lposc(void) { + sleep_run_from_dormant_source(DORMANT_SOURCE_LPOSC); +} +#endif + /*! \brief Set the dormant clock source to be the ring oscillator * \ingroup hardware_sleep */ @@ -62,10 +71,32 @@ static inline void sleep_run_from_rosc(void) { * * One of the sleep_run_* functions must be called prior to this call * - * \param t The time to wake up + * \param ts The time to wake up * \param callback Function to call on wakeup. */ -void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback); +void sleep_goto_sleep_until(struct timespec *ts, aon_timer_alarm_handler_t callback); + +/*! \brief Send system to sleep for a specified duration in milliseconds. This provides an alternative to sleep_goto_sleep_until +to allow for shorter duration sleeps. + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param delay_ms The duration to sleep for in milliseconds. + * \param callback Function to call on wakeup. + * \return Returns true if the device went to sleep + */ +bool sleep_goto_sleep_for(uint32_t delay_ms, hardware_alarm_callback_t callback); + +/*! \brief Send system to dormant until the specified time, note for RP2040 the RTC must be driven by an external clock + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param ts The time to wake up + * \param callback Function to call on wakeup. + */ +void sleep_goto_dormant_until(struct timespec *ts, aon_timer_alarm_handler_t callback); /*! \brief Send system to sleep until the specified GPIO changes * \ingroup hardware_sleep @@ -76,6 +107,7 @@ void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback); * \param edge true for leading edge, false for trailing edge * \param high true for active high, false for active low */ + void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high); /*! \brief Send system to sleep until a leading high edge is detected on GPIO @@ -100,8 +132,16 @@ static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) { sleep_goto_dormant_until_pin(gpio_pin, false, true); } +/*! \brief Reconfigure clocks to wake up properly from sleep/dormant mode + * \ingroup hardware_sleep + * + * This must be called immediately after continuing execution when waking up from sleep/dormant mode + * + */ +void sleep_power_up(void); + #ifdef __cplusplus } #endif -#endif \ No newline at end of file +#endif diff --git a/src/rp2_common/pico_sleep/sleep.c b/src/rp2_common/pico_sleep/sleep.c old mode 100644 new mode 100755 index 8de37fb..d64d35e --- a/src/rp2_common/pico_sleep/sleep.c +++ b/src/rp2_common/pico_sleep/sleep.c @@ -4,21 +4,35 @@ * SPDX-License-Identifier: BSD-3-Clause */ +#include +#include + #include "pico.h" #include "pico/stdlib.h" #include "pico/sleep.h" -#include "hardware/rtc.h" #include "hardware/pll.h" +#include "hardware/regs/clocks.h" #include "hardware/clocks.h" +#include "hardware/watchdog.h" #include "hardware/xosc.h" #include "hardware/rosc.h" #include "hardware/regs/io_bank0.h" // For __wfi #include "hardware/sync.h" +#include "pico/runtime_init.h" + +#ifdef __riscv +#include "hardware/riscv.h" +#else // For scb_hw so we can enable deep sleep #include "hardware/structs/scb.h" +#endif + +#if !PICO_RP2040 +#include "hardware/powman.h" +#endif // The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, // until the source (either xosc or rosc) is started again by an external event. @@ -26,13 +40,22 @@ // block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) // can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. - -// TODO: Optionally, memories can also be powered down. - static dormant_source_t _dormant_source; -bool dormant_source_valid(dormant_source_t dormant_source) { - return (dormant_source == DORMANT_SOURCE_XOSC) || (dormant_source == DORMANT_SOURCE_ROSC); +bool dormant_source_valid(dormant_source_t dormant_source) +{ + switch (dormant_source) { + case DORMANT_SOURCE_XOSC: + return true; + case DORMANT_SOURCE_ROSC: + return true; +#if !PICO_RP2040 + case DORMANT_SOURCE_LPOSC: + return true; +#endif + default: + return false; + } } // In order to go into dormant mode we need to be running from a stoppable clock source: @@ -42,11 +65,26 @@ void sleep_run_from_dormant_source(dormant_source_t dormant_source) { assert(dormant_source_valid(dormant_source)); _dormant_source = dormant_source; - // FIXME: Just defining average rosc freq here. - uint src_hz = (dormant_source == DORMANT_SOURCE_XOSC) ? XOSC_HZ : 6.5 * MHZ; - uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ? - CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC : - CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; + uint src_hz; + uint clk_ref_src; + switch (dormant_source) { + case DORMANT_SOURCE_XOSC: + src_hz = XOSC_HZ; + clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC; + break; + case DORMANT_SOURCE_ROSC: + src_hz = 6500 * KHZ; // todo + clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; + break; +#if !PICO_RP2040 + case DORMANT_SOURCE_LPOSC: + src_hz = 32 * KHZ; + clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_LPOSC_CLKSRC; + break; +#endif + default: + hard_assert(false); + } // CLK_REF = XOSC or ROSC clock_configure(clk_ref, @@ -62,15 +100,17 @@ void sleep_run_from_dormant_source(dormant_source_t dormant_source) { src_hz, src_hz); - // CLK USB = 0MHz - clock_stop(clk_usb); - // CLK ADC = 0MHz clock_stop(clk_adc); + clock_stop(clk_usb); +#if PICO_RP2350 + clock_stop(clk_hstx); +#endif +#if PICO_RP2040 // CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc - uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? - CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : + uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; clock_configure(clk_rtc, @@ -78,6 +118,7 @@ void sleep_run_from_dormant_source(dormant_source_t dormant_source) { clk_rtc_src, src_hz, 46875); +#endif // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable clock_configure(clk_peri, @@ -102,25 +143,80 @@ void sleep_run_from_dormant_source(dormant_source_t dormant_source) { setup_default_uart(); } -// Go to sleep until woken up by the RTC -void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) { - // We should have already called the sleep_run_from_dormant_source function - assert(dormant_source_valid(_dormant_source)); +static void processor_deep_sleep(void) { + // Enable deep sleep at the proc +#ifdef __riscv + uint32_t bits = RVCSR_MSLEEP_POWERDOWN_BITS; + if (!get_core_num()) { + bits |= RVCSR_MSLEEP_DEEPSLEEP_BITS; + } + riscv_set_csr(RVCSR_MSLEEP_OFFSET, bits); +#else + scb_hw->scr |= ARM_CPU_PREFIXED(SCR_SLEEPDEEP_BITS); +#endif +} - // Turn off all clocks when in sleep mode except for RTC +void sleep_goto_sleep_until(struct timespec *ts, aon_timer_alarm_handler_t callback) +{ + + // We should have already called the sleep_run_from_dormant_source function + // This is only needed for dormancy although it saves power running from xosc while sleeping + //assert(dormant_source_valid(_dormant_source)); + +#if PICO_RP2040 clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; clocks_hw->sleep_en1 = 0x0; +#else + clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_REF_POWMAN_BITS; + clocks_hw->sleep_en1 = 0x0; +#endif - rtc_set_alarm(t, callback); + aon_timer_enable_alarm(ts, callback, false); + + stdio_flush(); - uint save = scb_hw->scr; // Enable deep sleep at the proc - scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS; + processor_deep_sleep(); // Go to sleep __wfi(); } +bool sleep_goto_sleep_for(uint32_t delay_ms, hardware_alarm_callback_t callback) +{ + // We should have already called the sleep_run_from_dormant_source function + // This is only needed for dormancy although it saves power running from xosc while sleeping + //assert(dormant_source_valid(_dormant_source)); + + // Turn off all clocks except for the timer + clocks_hw->sleep_en0 = 0x0; +#if PICO_RP2040 + clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS; +#elif PICO_RP2350 + clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_REF_TICKS_BITS | CLOCKS_SLEEP_EN1_CLK_SYS_TIMER0_BITS; +#else +#error Unknown processor +#endif + + int alarm_num = hardware_alarm_claim_unused(true); + hardware_alarm_set_callback(alarm_num, callback); + absolute_time_t t = make_timeout_time_ms(delay_ms); + if (hardware_alarm_set_target(alarm_num, t)) { + hardware_alarm_set_callback(alarm_num, NULL); + hardware_alarm_unclaim(alarm_num); + return false; + } + + stdio_flush(); + + // Enable deep sleep at the proc + processor_deep_sleep(); + + // Go to sleep + __wfi(); + return true; +} + static void _go_dormant(void) { assert(dormant_source_valid(_dormant_source)); @@ -131,6 +227,34 @@ static void _go_dormant(void) { } } +void sleep_goto_dormant_until(struct timespec *ts, aon_timer_alarm_handler_t callback) { + // We should have already called the sleep_run_from_dormant_source function + +#if PICO_RP2040 + clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; + clocks_hw->sleep_en1 = 0x0; +#else + assert(_dormant_source == DORMANT_SOURCE_LPOSC); + uint64_t restore_ms = powman_timer_get_ms(); + powman_timer_set_1khz_tick_source_lposc(); + powman_timer_set_ms(restore_ms); + + clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_REF_POWMAN_BITS; + clocks_hw->sleep_en1 = 0x0; +#endif + + // Set the AON timer to wake up the proc from dormant mode + aon_timer_enable_alarm(ts, callback, true); + + stdio_flush(); + + // Enable deep sleep at the proc + processor_deep_sleep(); + + // Go dormant + _go_dormant(); +} + void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { bool low = !high; bool level = !edge; @@ -145,6 +269,8 @@ void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { if (edge && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS; if (edge && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS; + gpio_init(gpio_pin); + gpio_set_input_enabled(gpio_pin, true); gpio_set_dormant_irq_enabled(gpio_pin, event, true); _go_dormant(); @@ -152,4 +278,29 @@ void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { // Clear the irq so we can go back to dormant mode again if we want gpio_acknowledge_irq(gpio_pin, event); -} \ No newline at end of file + gpio_set_input_enabled(gpio_pin, false); +} + +// To be called after waking up from sleep/dormant mode to restore system clocks properly +void sleep_power_up(void) +{ + // Re-enable the ring oscillator, which will essentially kickstart the proc + rosc_enable(); + + // Reset the sleep enable register so peripherals and other hardware can be used + clocks_hw->sleep_en0 |= ~(0u); + clocks_hw->sleep_en1 |= ~(0u); + + // Restore all clocks + clocks_init(); + +#if PICO_RP2350 + // make powerman use xosc again + uint64_t restore_ms = powman_timer_get_ms(); + powman_timer_set_1khz_tick_source_xosc(); + powman_timer_set_ms(restore_ms); +#endif + + // UART needs to be reinitialised with the new clock frequencies for stable output + setup_default_uart(); +}