diff --git a/src/cmake/rp2_common.cmake b/src/cmake/rp2_common.cmake index 273839f70..a5a090927 100644 --- a/src/cmake/rp2_common.cmake +++ b/src/cmake/rp2_common.cmake @@ -49,6 +49,7 @@ pico_add_subdirectory(rp2_common/hardware_pio) pico_add_subdirectory(rp2_common/hardware_pll) pico_add_subdirectory(rp2_common/hardware_pwm) pico_add_subdirectory(rp2_common/hardware_resets) +pico_add_subdirectory(rp2_common/hardware_rosc) if (PICO_RP2040 OR PICO_COMBINED_DOCS) pico_add_subdirectory(rp2_common/hardware_rtc) endif() @@ -103,6 +104,7 @@ if (NOT PICO_BARE_METAL) pico_add_subdirectory(rp2_common/pico_int64_ops) pico_add_subdirectory(rp2_common/pico_flash) pico_add_subdirectory(rp2_common/pico_float) + pico_add_subdirectory(rp2_common/pico_low_power) pico_add_subdirectory(rp2_common/pico_mem_ops) pico_add_subdirectory(rp2_common/pico_malloc) pico_add_subdirectory(rp2_common/pico_printf) diff --git a/src/common/pico_base_headers/BUILD.bazel b/src/common/pico_base_headers/BUILD.bazel index 4a859a3f2..610865bc1 100644 --- a/src/common/pico_base_headers/BUILD.bazel +++ b/src/common/pico_base_headers/BUILD.bazel @@ -118,6 +118,7 @@ cc_library( "//src/rp2_common/hardware_ticks:__pkg__", "//src/rp2_common/hardware_timer:__pkg__", "//src/rp2_common/hardware_watchdog:__pkg__", + "//src/rp2_common/hardware_rosc:__pkg__", "//src/rp2_common/hardware_xosc:__pkg__", "//src/rp2_common/pico_crt0:__pkg__", "//src/rp2_common/pico_platform_common:__pkg__", diff --git a/src/common/pico_util/BUILD.bazel b/src/common/pico_util/BUILD.bazel index d1e73c0cb..a7a70c4aa 100644 --- a/src/common/pico_util/BUILD.bazel +++ b/src/common/pico_util/BUILD.bazel @@ -11,6 +11,7 @@ cc_library( "queue.c", ], hdrs = [ + "include/pico/util/bitset.h", "include/pico/util/datetime.h", "include/pico/util/pheap.h", "include/pico/util/queue.h", diff --git a/src/common/pico_util/include/pico/util/bitset.h b/src/common/pico_util/include/pico/util/bitset.h new file mode 100644 index 000000000..4327607af --- /dev/null +++ b/src/common/pico_util/include/pico/util/bitset.h @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_UTIL_BITSET_H +#define _PICO_UTIL_BITSET_H + +#include "pico.h" + +/** \file bitset.h + * \defgroup bitset bitset + * \brief Simple bitset implementation + * + * \ingroup pico_util + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t size; \ + uint16_t word_size; \ + uint32_t words[]; +} generic_bitset_t; + +/*! \brief Macro used to define a bitset type + * \ingroup pico_util + * This macro is used to define a bitset type. It is used as follows: + * ``` + * typedef bitset_type_t(32) my_bitset_t; + * ``` + * will define a new bitset type called `my_bitset_t` that can hold 32 bits. + * + * The type can be used as `my_bitset_t bitset;` to declare a new bitset. + * + * \param N the number of bits in the bitset + */ +#define bitset_type_t(N) union { \ + generic_bitset_t bitset; \ + struct { \ + uint16_t size; \ + uint16_t word_size; \ + uint32_t words[((N) + 31) / 32]; \ + } sized_bitset; \ +} +#define bitset_sizeof_for(N) ((((N) + 63u) / 32u) * 4u) + +/*! \brief Macro used to create a bitset with all bits set to a value + * \ingroup pico_util + * \param type the type of the bitset + * \param N the number of bits in the bitset + * \param value the value to set the bits to (0 or 1) + * \return the bitset + */ +#define bitset_with_value(type, N, value) ({ type bitset; bitset_init(&bitset, type, N, value); bitset; }) + +// Quick test that the bitset macros give the correct size +extern bitset_type_t(32) __not_real_bitset32; +extern bitset_type_t(33) __not_real_bitset33; +static_assert(sizeof(__not_real_bitset32) == bitset_sizeof_for(1),""); +static_assert(sizeof(__not_real_bitset33) == bitset_sizeof_for(37), ""); + +/*! \brief Initialize a bitset + * \ingroup pico_util + * \param ptr the bitset to initialize + * \param type the type of the bitset + * \param N the number of bits in the bitset + * \param fill the value to fill the bitset with (0 or 1) + */ +#define bitset_init(ptr, type, N, fill) ({ \ + assert(sizeof(type) == bitset_sizeof_for(N)); \ + __unused type *type_check = ptr; \ + __builtin_memset(ptr, (fill) ? 0xff : 0, sizeof(type)); \ + (ptr)->bitset.size = N; \ + (ptr)->bitset.word_size = ((N) + 31u) / 32u; \ +}) + +/*! \brief Get the size of the bitset + * \ingroup pico_util + * \param bitset the bitset to get the size of + * \return the size of the bitset + */ +static inline uint bitset_size(const generic_bitset_t *bitset) { + return bitset->size; +} + +/*! \brief Get the size of the bitset in words + * \ingroup pico_util + * \param bitset the bitset to get the size of + * \return the size of the bitset in words + */ +static inline uint bitset_word_size(const generic_bitset_t *bitset) { + return bitset->word_size; +} + +/*! \brief Check that the bitset is valid + * \ingroup pico_util + * This function will assert if the bitset is not valid. + * \param bitset the bitset to check + */ +static inline void check_bitset(const generic_bitset_t *bitset) { + assert(bitset->word_size == (bitset->size + 31) / 32); +} + +/*! \brief Write a word in the bitset + * \ingroup pico_util + * \param bitset the bitset to write to + * \param word_num the word number to write to + * \param value the value to write to the word + * \return the bitset + */ +static inline generic_bitset_t *bitset_write_word(generic_bitset_t *bitset, uint word_num, uint32_t value) { + check_bitset(bitset); + if (word_num < bitset_word_size(bitset)) { + bitset->words[word_num] = value; + } + return bitset; +} + +/*! \brief Read a word in the bitset + * \ingroup pico_util + * \param bitset the bitset to read from + * \param word_num the word number to read from + * \return the value of the word + */ +static inline uint32_t bitset_read_word(const generic_bitset_t *bitset, uint word_num) { + check_bitset(bitset); + if (word_num < bitset_word_size(bitset)) { + return bitset->words[word_num]; + } + return 0; +} + +/*! \brief Clear all bits in the bitset + * \ingroup pico_util + * \param bitset the bitset to clear + * \return the bitset + */ +static inline generic_bitset_t *bitset_clear(generic_bitset_t *bitset) { + check_bitset(bitset); + __builtin_memset(bitset->words, 0, bitset->word_size * sizeof(uint32_t)); + return bitset; +} + +/*! \brief Set all bits in the bitset + * \ingroup pico_util + * \param bitset the bitset to set + * \return the bitset + */ +static inline generic_bitset_t *bitset_set_all(generic_bitset_t *bitset) { + check_bitset(bitset); + __builtin_memset(bitset->words, 0xff, bitset->word_size * sizeof(uint32_t)); + return bitset; +} + +/*! \brief Set a single bit in the bitset + * \ingroup pico_util + * \param bitset the bitset to set + * \param bit the bit to set + * \return the bitset + */ +static inline generic_bitset_t *bitset_set_bit(generic_bitset_t *bitset, uint bit) { + check_bitset(bitset); + if (bit < bitset->size) { + bitset->words[bit / 32u] |= 1u << (bit % 32u); + } + return bitset; +} + +/*! \brief Clear a single bit in the bitset + * \ingroup pico_util + * \param bitset the bitset to clear + * \param bit the bit to clear + * \return the bitset + */ +static inline generic_bitset_t *bitset_clear_bit(generic_bitset_t *bitset, uint bit) { + check_bitset(bitset); + if (bit < bitset->size) { + bitset->words[bit / 32u] &= ~(1u << (bit % 32u)); + } + return bitset; +} + +/*! \brief Get the value of a single bit in the bitset + * \ingroup pico_util + * \param bitset the bitset to get the value of + * \param bit the bit to get the value of + * \return the value of the bit + */ +static inline bool bitset_get_bit(generic_bitset_t *bitset, uint bit) { + check_bitset(bitset); + assert(bit < bitset->size); +// if (bit < bitset->size) { + return bitset->words[bit / 32u] & (1u << (bit % 32u)); +// } + return false; +} + +/*! \brief Check if two bitsets are equal + * \ingroup pico_util + * \param bitset1 the first bitset to check + * \param bitset2 the second bitset to check + * \return true if the bitsets are equal, false otherwise + */ +static inline bool bitset_equal(const generic_bitset_t *bitset1, const generic_bitset_t *bitset2) { + check_bitset(bitset1); + check_bitset(bitset2); + assert(bitset1->size == bitset2->size); + return __builtin_memcmp(bitset1->words, bitset2->words, bitset1->word_size * sizeof(uint32_t)) == 0; +} + +typedef uint32_t tiny_encoded_bitset_t; +typedef uint64_t encoded_bitset_t; + +#define encoded_bitset_empty() 0 +#define encoded_bitset_of1(v) (1u | ((v) << 8)) +#define encoded_bitset_of2(v1, v2) (2u | ((v1) << 8) | ((v2) << 16)) +#define encoded_bitset_of3(v1, v2, v3) (3u | ((v1) << 8) | ((v2) << 16) | (((v3) << 24))) +#define encoded_bitset_of4(v1, v2, v3, v4) (4u | ((v1) << 8) | ((v2) << 16) | (((v3) << 24)) | (((uint64_t)(v4)) << 32)) +#define encoded_bitset_of5(v1, v2, v3, v4, v5) (5u | ((v1) << 8) | ((v2) << 16) | (((v3) << 24)) | (((uint64_t)((v4) | ((v5)<<8u))) << 32)) + +#define encoded_bitset_foreach(bitset, x) ({ \ + for(uint _i=0;_i<((bitset)&0xffu);_i++) { \ + uint bit = (uint8_t)((bitset) >> (8 * _i)); \ + x; \ + } \ +}) +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/rp2_common/BUILD.bazel b/src/rp2_common/BUILD.bazel index fe1c0c3e3..d5ccf6555 100644 --- a/src/rp2_common/BUILD.bazel +++ b/src/rp2_common/BUILD.bazel @@ -73,6 +73,7 @@ alias( "//src/rp2_common/hardware_pll:__pkg__", "//src/rp2_common/hardware_vreg:__pkg__", "//src/rp2_common/hardware_watchdog:__pkg__", + "//src/rp2_common/hardware_rosc:__pkg__", "//src/rp2_common/hardware_xosc:__pkg__", "//src/rp2_common/pico_bit_ops:__pkg__", "//src/rp2_common/pico_bootrom:__pkg__", diff --git a/src/rp2_common/hardware_clocks/BUILD.bazel b/src/rp2_common/hardware_clocks/BUILD.bazel index 29c1bb104..e00d05229 100644 --- a/src/rp2_common/hardware_clocks/BUILD.bazel +++ b/src/rp2_common/hardware_clocks/BUILD.bazel @@ -13,10 +13,12 @@ cc_library( target_compatible_with = compatible_with_rp2(), visibility = [ "//src/rp2_common/hardware_pll:__pkg__", + "//src/rp2_common/hardware_rosc:__pkg__", "//src/rp2_common/hardware_xosc:__pkg__", ], deps = [ "//src/common/pico_base_headers", + "//src/common/pico_util", "//src/rp2_common:hardware_structs", "//src/rp2_common/hardware_base", ], diff --git a/src/rp2_common/hardware_clocks/clocks.c b/src/rp2_common/hardware_clocks/clocks.c index 2bcaa5741..8243b6e73 100644 --- a/src/rp2_common/hardware_clocks/clocks.c +++ b/src/rp2_common/hardware_clocks/clocks.c @@ -441,3 +441,18 @@ bool check_sys_clock_khz(uint32_t freq_khz, uint *vco_out, uint *postdiv1_out, u } return false; } + + +void clock_get_sleep_en_gate(clock_dest_bitset_t *dests) { + static_assert(CLOCKS_SLEEP_EN1_OFFSET == CLOCKS_SLEEP_EN0_OFFSET + 4, ""); + for(uint i=0;i < bitset_word_size(&dests->bitset); i++) { + bitset_write_word(&dests->bitset, i, clocks_hw->sleep_en[i]); + } +} + +void clock_gate_sleep_en(const clock_dest_bitset_t *dests) { + static_assert(CLOCKS_SLEEP_EN1_OFFSET == CLOCKS_SLEEP_EN0_OFFSET + 4, ""); + for(uint i=0;i < bitset_word_size(&dests->bitset); i++) { + clocks_hw->sleep_en[i] = bitset_read_word(&dests->bitset, i); + } +} diff --git a/src/rp2_common/hardware_clocks/include/hardware/clocks.h b/src/rp2_common/hardware_clocks/include/hardware/clocks.h index 9e1c3f4a6..397d8b82a 100644 --- a/src/rp2_common/hardware_clocks/include/hardware/clocks.h +++ b/src/rp2_common/hardware_clocks/include/hardware/clocks.h @@ -578,6 +578,35 @@ static inline bool set_sys_clock_khz(uint32_t freq_khz, bool required) { return false; } +#include "pico/util/bitset.h" + +typedef bitset_type_t(NUM_CLOCK_DESTINATIONS) clock_dest_bitset_t; +#define clock_dest_bitset_none() bitset_with_value(clock_dest_bitset_t, NUM_CLOCK_DESTINATIONS, 0) +#define clock_dest_bitset_all() bitset_with_value(clock_dest_bitset_t, NUM_CLOCK_DESTINATIONS, 1) + +static inline clock_dest_bitset_t *clock_dest_bitset_clear(clock_dest_bitset_t *dests) { + bitset_clear(&dests->bitset); + return dests; +} + +static inline clock_dest_bitset_t *clock_dest_bitset_add_all(clock_dest_bitset_t *dests) { + bitset_set_all(&dests->bitset); + return dests; +} + +static inline clock_dest_bitset_t *clock_dest_bitset_add(clock_dest_bitset_t *dests, clock_dest_num_t dest) { + bitset_set_bit(&dests->bitset, dest); + return dests; +} + +static inline clock_dest_bitset_t *clock_dest_bitset_remove(clock_dest_bitset_t *dests, clock_dest_num_t dest) { + bitset_clear_bit(&dests->bitset, dest); + return dests; +} + +void clock_get_sleep_en_gate(clock_dest_bitset_t *clocks); +void clock_gate_sleep_en(const clock_dest_bitset_t *clocks); + #define GPIO_TO_GPOUT_CLOCK_HANDLE_RP2040(gpio, default_clk_handle) \ ((gpio) == 21 ? clk_gpout0 : \ ((gpio) == 23 ? clk_gpout1 : \ diff --git a/src/rp2_common/hardware_irq/include/hardware/irq.h b/src/rp2_common/hardware_irq/include/hardware/irq.h index 0dcc49d59..b1b7b31a1 100644 --- a/src/rp2_common/hardware_irq/include/hardware/irq.h +++ b/src/rp2_common/hardware_irq/include/hardware/irq.h @@ -276,6 +276,21 @@ void irq_set_mask_enabled(uint32_t mask, bool enabled); */ void irq_set_mask_n_enabled(uint n, uint32_t mask, bool enabled); +/*! \brief Get the current enabled mask on the executing core + * \ingroup hardware_irq + * + * \return mask 32-bit mask with one bits set for the enabled interrupts \ref interrupt_nums + */ + uint32_t irq_get_mask(void); + + /*! \brief Get the current enabled mask on the executing core + * \ingroup hardware_irq + * + * \param n the index of the mask to update. n == 0 means 0->31, n == 1 mean 32->63 etc. + * \return mask 32-bit mask with one bits set for the enabled interrupts \ref interrupt_nums + */ + uint32_t irq_get_mask_n(uint n); + /*! \brief Set an exclusive interrupt handler for an interrupt on the executing core. * \ingroup hardware_irq * diff --git a/src/rp2_common/hardware_irq/irq.c b/src/rp2_common/hardware_irq/irq.c index fdcbb00fb..2bdc1f656 100644 --- a/src/rp2_common/hardware_irq/irq.c +++ b/src/rp2_common/hardware_irq/irq.c @@ -113,6 +113,27 @@ void irq_set_mask_n_enabled(uint n, uint32_t mask, bool enabled) { irq_set_mask_n_enabled_internal(n, mask, enabled); } +static inline uint32_t irq_get_mask_n_internal(uint n) { + invalid_params_if(HARDWARE_IRQ, n * 32u >= ((PICO_NUM_VTABLE_IRQS + 31u) & ~31u)); +#if defined(__riscv) + return (hazard3_irqarray_read(RVCSR_MEIEA_OFFSET, 2 * n) & 0xffffu) | (hazard3_irqarray_read(RVCSR_MEIEA_OFFSET, 2 * n + 1) << 16); +#elif PICO_RP2040 + ((void)n); + return nvic_hw->iser; +#else + // >32 IRQs + return nvic_hw->iser[n]; +#endif +} + +uint32_t irq_get_mask(void) { + return irq_get_mask_n_internal(0); +} + +uint32_t irq_get_mask_n(uint n) { + return irq_get_mask_n_internal(n); +} + void irq_set_pending(uint num) { check_irq_param(num); #ifdef __riscv diff --git a/src/rp2_common/hardware_powman/include/hardware/powman.h b/src/rp2_common/hardware_powman/include/hardware/powman.h index 201b0cc34..25c5a6397 100644 --- a/src/rp2_common/hardware_powman/include/hardware/powman.h +++ b/src/rp2_common/hardware_powman/include/hardware/powman.h @@ -9,6 +9,7 @@ #include "pico.h" #include "hardware/structs/powman.h" +#include "pico/util/bitset.h" #ifdef __cplusplus extern "C" { @@ -167,9 +168,53 @@ enum powman_power_domains { POWMAN_POWER_DOMAIN_SWITCHED_CORE = 3, ///< Switched core logic (processors, busfabric, peris etc) POWMAN_POWER_DOMAIN_COUNT = 4, }; +typedef enum powman_power_domains powman_power_domain_t; typedef uint32_t powman_power_state; +typedef bitset_type_t(POWMAN_POWER_DOMAIN_COUNT) pstate_bitset_t; +#define pstate_bitset_none() bitset_with_value(pstate_bitset_t, POWMAN_POWER_DOMAIN_COUNT, 0) +#define pstate_bitset_all() bitset_with_value(pstate_bitset_t, POWMAN_POWER_DOMAIN_COUNT, 1) + +static inline pstate_bitset_t *pstate_bitset_clear(pstate_bitset_t *domains) { + bitset_clear(&domains->bitset); + return domains; +} + +static inline pstate_bitset_t *pstate_bitset_add_all(pstate_bitset_t *domains) { + bitset_set_all(&domains->bitset); + return domains; +} + +static inline pstate_bitset_t *pstate_bitset_add(pstate_bitset_t *domains, powman_power_domain_t domain) { + bitset_set_bit(&domains->bitset, domain); + return domains; +} + +static inline pstate_bitset_t *pstate_bitset_remove(pstate_bitset_t *domains, powman_power_domain_t domain) { + bitset_clear_bit(&domains->bitset, domain); + return domains; +} + +static inline bool pstate_bitset_is_set(pstate_bitset_t *domains, powman_power_domain_t domain) { + return bitset_get_bit(&domains->bitset, domain); +} + +static inline bool pstate_bitset_none_set(pstate_bitset_t *domains) { + pstate_bitset_t none = pstate_bitset_none(); + return bitset_equal(&domains->bitset, &none.bitset); +} + +static inline pstate_bitset_t *pstate_bitset_from_powman_power_state(pstate_bitset_t *domains, powman_power_state pstate) { + static_assert(sizeof(powman_power_state) <= sizeof(uint32_t)); + bitset_write_word(&domains->bitset, 0, pstate); + return domains; +} + +static inline powman_power_state pstate_bitset_to_powman_power_state(pstate_bitset_t *domains) { + return bitset_read_word(&domains->bitset, 0); +} + /*! \brief Get the current power state * \ingroup hardware_powman */ @@ -180,8 +225,8 @@ powman_power_state powman_get_power_state(void); * * Check the desired state is valid. Powman will go to the state if it is valid and there are no pending power up requests. * - * Note that if you are turning off the switched core then this function will never return as the processor will have - * been turned off at the end. + * Note that if you are turning off the switched core then you need to call __wfi() after this function returns, otherwise + * the transition will not take place. * * \param state the power state to go to * \returns PICO_OK if the state is valid. Misc PICO_ERRORs are returned if not diff --git a/src/rp2_common/hardware_rosc/BUILD.bazel b/src/rp2_common/hardware_rosc/BUILD.bazel new file mode 100644 index 000000000..2da676ae6 --- /dev/null +++ b/src/rp2_common/hardware_rosc/BUILD.bazel @@ -0,0 +1,19 @@ +load("//bazel:defs.bzl", "compatible_with_rp2") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "hardware_rosc", + srcs = ["rosc.c"], + hdrs = ["include/hardware/rosc.h"], + includes = ["include"], + target_compatible_with = compatible_with_rp2(), + deps = [ + "//src/common/pico_base_headers", + "//src/rp2_common:hardware_regs", + "//src/rp2_common:hardware_structs", + "//src/rp2_common:pico_platform_internal", + "//src/rp2_common:platform_defs", + "//src/rp2_common/hardware_clocks:hardware_clocks_headers", + ], +) diff --git a/src/rp2_common/hardware_rosc/CMakeLists.txt b/src/rp2_common/hardware_rosc/CMakeLists.txt new file mode 100644 index 000000000..83a6c3732 --- /dev/null +++ b/src/rp2_common/hardware_rosc/CMakeLists.txt @@ -0,0 +1,2 @@ +pico_simple_hardware_target(rosc) +pico_mirrored_target_link_libraries(hardware_rosc INTERFACE hardware_clocks) diff --git a/src/rp2_common/hardware_rosc/include/hardware/rosc.h b/src/rp2_common/hardware_rosc/include/hardware/rosc.h new file mode 100644 index 000000000..5868236ca --- /dev/null +++ b/src/rp2_common/hardware_rosc/include/hardware/rosc.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _HARDWARE_ROSC_H_ +#define _HARDWARE_ROSC_H_ + +#include "pico.h" +#include "hardware/structs/rosc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file rosc.h + * \defgroup hardware_rosc hardware_rosc + * + * Ring Oscillator (ROSC) API + * + * A Ring Oscillator is an on-chip oscillator that requires no external crystal. Instead, the output is generated from a series of + * inverters that are chained together to create a feedback loop. RP2 chips boot from the ring oscillator initially, meaning the + * first stages of the bootrom, including booting from SPI flash, will be clocked by the ring oscillator. If your design has a + * crystal oscillator, you’ll likely want to switch to this as your reference clock as soon as possible, because the frequency is + * more accurate than the ring oscillator. + */ + +/*! \brief Set frequency of the Ring Oscillator + * \ingroup hardware_rosc + * + * \param code The drive strengths. See the datasheet for information on this value. + */ +void rosc_set_freq(uint32_t code); + +/*! \brief Set range of the Ring Oscillator + * \ingroup hardware_rosc + * + * Frequency range. Frequencies will vary with Process, Voltage & Temperature (PVT). + * Clock output will not glitch when changing the range up one step at a time. + * + * \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High. + */ +void rosc_set_range(uint range); + +/*! \brief Disable the Ring Oscillator + * \ingroup hardware_rosc + * + */ +void rosc_disable(void); + +/*! \brief Put Ring Oscillator in to dormant mode. + * \ingroup hardware_rosc + * + * The ROSC supports a dormant mode, which stops oscillation until woken up up by an asynchronous interrupt. + * This can either come from the RTC, being clocked by an external clock, or a GPIO pin going high or low. + * If no IRQ is configured before going into dormant mode the ROSC will never restart. + * + * PLLs should be stopped before selecting dormant mode. + */ +void rosc_set_dormant(void); + +/*! \brief Re-enable the ring oscillator so that the processor cores can wake up after sleep/dormant mode + \ingroup hardware_rosc + + This must be called at the end of the sleeping period (e.g., in an interrupt service routine) +*/ +void rosc_restart(void); + +/*! \brief Get the next ROSC freq code + * \ingroup hardware_rosc + * + * Given a ROSC freq code, return the next-numerically-higher code. + * Top result bit is set when called on maximum ROSC code. + * + * \param code The current ROSC freq code. + * \return The next ROSC freq code. + */ +uint32_t next_rosc_code(uint32_t code); + +/*! \brief Set the frequency of the Ring Oscillator within a range + * \ingroup hardware_rosc + * + * This function will set the frequency of the Ring Oscillator to the first frequency within the range. + * + * \param low_mhz The bottom of the range to search for. + * \param high_mhz The top of the range to search for. + * \return The frequency of the Ring Oscillator within the range in MHz, or 0 if no frequency within the range is found. + */ +uint rosc_find_freq_mhz(uint32_t low_mhz, uint32_t high_mhz); + +/*! \brief Measure the frequency of the Ring Oscillator + * \ingroup hardware_rosc + * + * \return The frequency of the Ring Oscillator in kHz. + */ +uint rosc_measure_freq_khz(void); + +/*! \brief Set the output divider of the Ring Oscillator + * \ingroup hardware_rosc + * + * \if rp2040_specific + * div = 0 divides by 32 + * div = 1-31 divides by div + * any other value sets div=31 + * \endif + * + * \if rp2350_specific + * div = 0 divides by 128 + * div = 1-127 divides by div + * any other value sets div=128 + * \endif + * + * \param div The output divider. + */ +void rosc_set_div(uint32_t div); + +inline static void rosc_clear_bad_write(void) { + hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS); +} + +inline static bool rosc_write_okay(void) { + return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS); +} + +/*! \brief Checked write to a Ring Oscillator register + * \ingroup hardware_rosc + * + * Clears the bad write flag and asserts that the write is okay. + * + * \param addr The register address. + * \param value The value to write. + */ +inline static void rosc_write(io_rw_32 *addr, uint32_t value) { + rosc_clear_bad_write(); + assert(rosc_write_okay()); + *addr = value; + assert(rosc_write_okay()); +}; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/hardware_rosc/rosc.c b/src/rp2_common/hardware_rosc/rosc.c new file mode 100644 index 000000000..d24415e53 --- /dev/null +++ b/src/rp2_common/hardware_rosc/rosc.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico.h" + +// For MHZ definitions etc +#include "hardware/clocks.h" +#include "hardware/rosc.h" + +// Given a ROSC delay stage code, return the next-numerically-higher code. +// Top result bit is set when called on maximum ROSC code. +uint32_t next_rosc_code(uint32_t code) { + return ((code | 0x08888888u) + 1u) & 0xf7777777u; +} + +uint rosc_find_freq_mhz(uint32_t low_mhz, uint32_t high_mhz) { + // TODO: This could be a lot better + rosc_set_div(1); + for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) { + rosc_set_freq(code); + uint rosc_mhz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000; + if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) { + return rosc_mhz; + } + } + return 0; +} + +uint rosc_measure_freq_khz(void) { + return frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC); +} + +void rosc_set_div(uint32_t div) { +#if PICO_RP2040 + assert(div <= 31 && div >= 0); +#else + assert(div <= 127 && div >= 0); +#endif + rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div); +} + +void rosc_set_freq(uint32_t code) { + rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu)); + rosc_write(&rosc_hw->freqb, (ROSC_FREQB_PASSWD_VALUE_PASS << ROSC_FREQB_PASSWD_LSB) | (code >> 16u)); +} + +void rosc_set_range(uint range) { + // Range should use enumvals from the headers and thus have the password correct + rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range); +} + +void rosc_disable(void) { + uint32_t tmp = rosc_hw->ctrl; + tmp &= (~ROSC_CTRL_ENABLE_BITS); + tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB); + rosc_write(&rosc_hw->ctrl, tmp); + // Wait for stable to go away + while (rosc_hw->status & ROSC_STATUS_STABLE_BITS); +} + +void rosc_set_dormant(void) { + // WARNING: This stops the rosc until woken up by an irq + 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)); +} + +void rosc_restart(void) { + //Re-enable the rosc + rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB); + + //Wait for it to become stable once restarted + while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); +} \ No newline at end of file diff --git a/src/rp2_common/hardware_rtc/include/hardware/rtc.h b/src/rp2_common/hardware_rtc/include/hardware/rtc.h index 9e05c7afb..6b5576c7f 100644 --- a/src/rp2_common/hardware_rtc/include/hardware/rtc.h +++ b/src/rp2_common/hardware_rtc/include/hardware/rtc.h @@ -90,6 +90,15 @@ void rtc_enable_alarm(void); */ void rtc_disable_alarm(void); +/*! \brief Run the RTC from an external clock source through GPIO + * \ingroup hardware_sleep + * + * \param src_hz The frequency of the external clock source + * \param gpio_pin The input pin providing the external clock (GP20 or GP22) + * \return true if it is possible to run the RTC from the external clock frequency, false otherwise. + */ +bool rtc_run_from_external_source(uint32_t src_hz, uint gpio_pin); + #ifdef __cplusplus } #endif diff --git a/src/rp2_common/hardware_rtc/rtc.c b/src/rp2_common/hardware_rtc/rtc.c index f3f61eabf..faba578c3 100644 --- a/src/rp2_common/hardware_rtc/rtc.c +++ b/src/rp2_common/hardware_rtc/rtc.c @@ -188,3 +188,7 @@ void rtc_disable_alarm(void) { tight_loop_contents(); } } + +bool rtc_run_from_external_source(uint32_t src_hz, uint gpio_pin) { + return clock_configure_gpin(clk_rtc, gpio_pin, src_hz, 46875); +} \ No newline at end of file diff --git a/src/rp2_common/pico_aon_timer/aon_timer.c b/src/rp2_common/pico_aon_timer/aon_timer.c index 62ef6c27a..b9a2e33e9 100644 --- a/src/rp2_common/pico_aon_timer/aon_timer.c +++ b/src/rp2_common/pico_aon_timer/aon_timer.c @@ -102,6 +102,12 @@ bool aon_timer_get_time_calendar(struct tm *tm) { #endif } +absolute_time_t aon_timer_get_absolute_time(void) { + struct timespec ts; + aon_timer_get_time(&ts); + return from_us_since_boot(timespec_to_us(&ts)); +} + aon_timer_alarm_handler_t aon_timer_enable_alarm(const struct timespec *ts, aon_timer_alarm_handler_t handler, bool wakeup_from_low_power) { #if HAS_RP2040_RTC struct tm tm; diff --git a/src/rp2_common/pico_aon_timer/include/pico/aon_timer.h b/src/rp2_common/pico_aon_timer/include/pico/aon_timer.h index b3095d8e0..f78b617dd 100644 --- a/src/rp2_common/pico_aon_timer/include/pico/aon_timer.h +++ b/src/rp2_common/pico_aon_timer/include/pico/aon_timer.h @@ -180,6 +180,13 @@ bool aon_timer_get_time(struct timespec *ts); */ bool aon_timer_get_time_calendar(struct tm *tm); +/** + * \brief Get the current time of the AON timer as an absolute time + * \ingroup pico_aon_timer + * \return the current time of the AON timer as an absolute time + */ +absolute_time_t aon_timer_get_absolute_time(void); + /** * \brief Get the resolution of the AON timer * \ingroup pico_aon_timer diff --git a/src/rp2_common/pico_clib_interface/llvm_libc_interface.c b/src/rp2_common/pico_clib_interface/llvm_libc_interface.c index dfcab015e..69e513c31 100644 --- a/src/rp2_common/pico_clib_interface/llvm_libc_interface.c +++ b/src/rp2_common/pico_clib_interface/llvm_libc_interface.c @@ -41,6 +41,11 @@ int settimeofday(__unused const struct timeval *tv, __unused const struct timezo return 0; } +// Some Clang versions don't support localtime_r, so we use gmtime_r instead. +__weak struct tm* localtime_r(const time_t* time, struct tm* tm) { + return gmtime_r((time_t*)time, tm); +} + // TODO: This should be a thread-local variable. int errno; diff --git a/src/rp2_common/pico_crt0/crt0.S b/src/rp2_common/pico_crt0/crt0.S index ea3b99a5a..fa113fbab 100644 --- a/src/rp2_common/pico_crt0/crt0.S +++ b/src/rp2_common/pico_crt0/crt0.S @@ -15,6 +15,10 @@ #include "boot/picobin.h" #include "pico/bootrom.h" +#if HAS_POWMAN_TIMER +#include "hardware/regs/powman.h" +#endif + // PICO_CONFIG: PICO_CRT0_NEAR_CALLS, Whether calls from crt0 into the binary are near (<16M away) - ignored for PICO_COPY_TO_RAM, default=0, type=bool, group=pico_crt0 #ifndef PICO_CRT0_NEAR_CALLS #define PICO_CRT0_NEAR_CALLS 0 @@ -493,6 +497,7 @@ _call_xip_setup: // Zero out the BSS ldr r1, =__bss_start__ ldr r2, =__bss_end__ +1: movs r0, #0 b bss_fill_test bss_fill_loop: @@ -501,6 +506,27 @@ bss_fill_test: cmp r1, r2 bne bss_fill_loop +#if LIB_PICO_LOW_POWER && HAS_POWMAN_TIMER + // Check if we're done + ldr r6, =__bss_end__ + cmp r2, r6 + bne 1f + // Zero out persistent_data on non-powman boot + ldr r1, =__persistent_data_start__ + ldr r2, =__persistent_data_end__ + // Skip if there is no persistent data + cmp r1, r2 + beq 1f + // Load previous power state into r6 + ldr r6, =(POWMAN_BASE+POWMAN_SCRATCH6_OFFSET) + ldr r6, [r6] + // Check if we should skip zeroing + bl persistent_zero_check + cmp r0, #0 + beq 1b +1: +#endif + platform_entry: // symbol for stack traces #if PICO_CRT0_NEAR_CALLS && !PICO_COPY_TO_RAM bl runtime_init @@ -532,6 +558,49 @@ data_cpy: bx lr #endif +#if LIB_PICO_LOW_POWER && HAS_POWMAN_TIMER +persistent_zero_check: + // uses r5 and r7 as scratch + // uses the powman_power_state in r6 + // returns in r0 + mov r0, #0 + // First check __persistent_data_start__ + ldr r7, =__persistent_data_start__ +check_r7: + ldr r5, =SRAM_BASE + cmp r5, r7 + bgt check_xip_sram + ldr r5, =SRAM4_BASE + cmp r5, r7 + bgt check_sram0 +check_sram1: + mov r5, #(1 << 0) // POWMAN_POWER_DOMAIN_SRAM_BANK1 + b do_check +check_xip_sram: + mov r5, #(1 << 2) // POWMAN_POWER_DOMAIN_XIP_CACHE + b do_check +check_sram0: + mov r5, #(1 << 1) // POWMAN_POWER_DOMAIN_SRAM_BANK0 + b do_check +do_check: + and r7, r5, r6 + cmp r5, r7 + beq skip + b noskip +skip: + cmp r0, #1 + bne check_end + bx lr +check_end: + add r0, #1 + // If start was skipped, check __persistent_data_end__ + ldr r7, =__persistent_data_end__ + b check_r7 +noskip: + mov r0, #0 + bx lr +#endif + // Note the data copy table is still included for NO_FLASH builds, even though // we skip the copy, because it is listed in binary info diff --git a/src/rp2_common/pico_crt0/crt0_riscv.S b/src/rp2_common/pico_crt0/crt0_riscv.S index 82864d017..31d39749f 100644 --- a/src/rp2_common/pico_crt0/crt0_riscv.S +++ b/src/rp2_common/pico_crt0/crt0_riscv.S @@ -12,6 +12,10 @@ #include "boot/picobin.h" #include "pico/bootrom_constants.h" +#if HAS_POWMAN_TIMER +#include "hardware/regs/powman.h" +#endif + #ifdef NDEBUG #ifndef COLLAPSE_IRQS #define COLLAPSE_IRQS @@ -369,6 +373,24 @@ bss_fill_loop: bss_fill_test: bne a1, a2, bss_fill_loop +#if LIB_PICO_LOW_POWER && HAS_POWMAN_TIMER + // Check if we're done + la a6, __bss_end__ + bne a2, a6, 1f + // Zero out persistent_data on non-powman boot + la a1, __persistent_data_start__ + la a2, __persistent_data_end__ + // Skip if there is no persistent data + beq a1, a2, 1f + // Load previous power state into r6 + li a6, POWMAN_BASE+POWMAN_SCRATCH6_OFFSET + lw a6, 0(a6) + // Check if we should skip zeroing + jal persistent_zero_check + beqz a0, bss_fill_test +1: +#endif + platform_entry: // symbol for stack traces // Use `call` pseudo-instruction instead of a bare `jal` so that the // linker can use longer sequences if these are out of `jal` range. Will @@ -383,6 +405,8 @@ platform_entry: // symbol for stack traces ebreak j 1b + +#if !PICO_NO_FLASH data_cpy_loop: lw a0, (a1) sw a0, (a2) @@ -391,6 +415,46 @@ data_cpy_loop: data_cpy: bltu a2, a3, data_cpy_loop ret +#endif + +#if LIB_PICO_LOW_POWER && HAS_POWMAN_TIMER +persistent_zero_check: + // uses a5 and a7 as scratch + // uses the powman_power_state in a6 + // returns in a0 + li a0, 0 + // First check __persistent_data_start__ + la a7, __persistent_data_start__ +check_a7: + li a5, SRAM_BASE + bgt a5, a7, check_xip_sram + li a5, SRAM4_BASE + bgt a5, a7, check_sram0 +check_sram1: + li a5, 1 << 0 // POWMAN_POWER_DOMAIN_SRAM_BANK1 + j do_check +check_xip_sram: + li a5, 1 << 2 // POWMAN_POWER_DOMAIN_XIP_CACHE + j do_check +check_sram0: + li a5, 1 << 1 // POWMAN_POWER_DOMAIN_SRAM_BANK0 + j do_check +do_check: + and a7, a5, a6 + beq a5, a7, skip + j noskip +skip: + beqz a0, check_end + ret +check_end: + addi a0, a0, 1 + // If start was skipped, check __persistent_data_end__ + la a7, __persistent_data_end__ + j check_a7 +noskip: + li a0, 0 + ret +#endif .align 2 data_cpy_table: diff --git a/src/rp2_common/pico_crt0/embedded_start_block.inc.S b/src/rp2_common/pico_crt0/embedded_start_block.inc.S index b76b34c79..000da904b 100644 --- a/src/rp2_common/pico_crt0/embedded_start_block.inc.S +++ b/src/rp2_common/pico_crt0/embedded_start_block.inc.S @@ -38,6 +38,10 @@ #define PICO_CRT0_IMAGE_TYPE_TBYB 0 #endif +#ifndef PICO_CRT0_PIN_XIP_SRAM +#define PICO_CRT0_PIN_XIP_SRAM 0 +#endif + #if PICO_CRT0_IMAGE_TYPE_TBYB #define CRT0_TBYB_FLAG PICOBIN_IMAGE_TYPE_EXE_TBYB_BITS #else @@ -120,6 +124,16 @@ embedded_block: .word __vectors #endif +#if PICO_CRT0_PIN_XIP_SRAM +.byte PICOBIN_BLOCK_ITEM_LOAD_MAP +.byte 0x04 // word size +.byte 0 // pad +.byte 0x01 // number of entries +.word 0 // clear +.word XIP_SRAM_BASE +.word 0 // size +#endif + .byte PICOBIN_BLOCK_ITEM_2BS_LAST .hword (embedded_block_end - embedded_block - 16 ) / 4 // total size of all .byte 0 diff --git a/src/rp2_common/pico_low_power/BUILD.bazel b/src/rp2_common/pico_low_power/BUILD.bazel new file mode 100644 index 000000000..796ea2894 --- /dev/null +++ b/src/rp2_common/pico_low_power/BUILD.bazel @@ -0,0 +1,25 @@ +load("//bazel:defs.bzl", "compatible_with_rp2") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "pico_low_power", + srcs = ["low_power.c"], + hdrs = ["include/pico/low_power.h"], + includes = ["include"], + target_compatible_with = compatible_with_rp2(), + deps = [ + "//src/common/pico_time", + "//src/common/pico_util", + "//src/rp2_common:hardware_structs", + "//src/rp2_common:pico_platform", + "//src/rp2_common/hardware_clocks", + "//src/rp2_common/hardware_rosc", + "//src/rp2_common/hardware_xosc", + "//src/rp2_common/hardware_irq", + "//src/rp2_common/hardware_timer", + "//src/rp2_common/hardware_uart", + "//src/rp2_common/pico_aon_timer", + "//src/rp2_common/pico_stdio", + ], +) diff --git a/src/rp2_common/pico_low_power/CMakeLists.txt b/src/rp2_common/pico_low_power/CMakeLists.txt new file mode 100644 index 000000000..a29748177 --- /dev/null +++ b/src/rp2_common/pico_low_power/CMakeLists.txt @@ -0,0 +1,56 @@ +pico_add_library(pico_low_power) + +target_sources(pico_low_power INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/low_power.c +) + +target_include_directories(pico_low_power INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/include +) + +pico_mirrored_target_link_libraries(pico_low_power INTERFACE + hardware_clocks + hardware_rosc + hardware_xosc + hardware_irq + hardware_timer + pico_aon_timer + hardware_uart + pico_stdio + ) + +# pico_set_persistent_data_loc(TARGET PERSISTENT_DATA_LOC) +# \brief\ Set the persistent data location for the target +# +# This sets the target property PICO_TARGET_PERSISTENT_DATA_LOC to the given value, +# and also sets the target property PICO_TARGET_HEAP_LOC and PICO_TARGET_HEAP_LIMIT +# to the appropriate values based on the persistent data location. +# +# It also sets PICO_CRT0_PIN_XIP_SRAM=1 to pin the XIP_SRAM, +# if the persistent data is stored in XIP_SRAM on RP2350. +# +# \param\ PERSISTENT_DATA_LOC The persistent data location to set +function(pico_set_persistent_data_loc TARGET PERSISTENT_DATA_LOC) + if (NOT PICO_RP2350) + message(FATAL_ERROR "pico_set_persistent_data_loc is only supported on RP2350") + endif() + + pico_set_linker_script_var(${TARGET} PERSISTENT_DATA_LOC ${PERSISTENT_DATA_LOC}) + + if (PERSISTENT_DATA_LOC LESS 0x20000000) + # XIP_SRAM, so heap should come after bss + pico_set_linker_script_var(${TARGET} HEAP_LOC __bss_end__) + # Also pin the XIP_SRAM + target_compile_definitions(${TARGET} PRIVATE PICO_CRT0_PIN_XIP_SRAM=1) + elseif (PERSISTENT_DATA_LOC LESS 0x20040000) + # SRAM0, so heap should come after persistent data + pico_set_linker_script_var(${TARGET} HEAP_LOC __persistent_data_end__) + elseif(PERSISTENT_DATA_LOC LESS 0x20080000) + # SRAM1, so heap should come before persistent data + pico_set_linker_script_var(${TARGET} HEAP_LOC __bss_end__) + pico_set_linker_script_var(${TARGET} HEAP_LIMIT ${PERSISTENT_DATA_LOC}) + else() + # Not supported in scratch, as the linker script will overwrite persistent data with scratch data + message(FATAL_ERROR "pico_set_persistent_data_loc only supports persistent data in XIP_SRAM or SRAM0-7") + endif() +endfunction() diff --git a/src/rp2_common/pico_low_power/include/pico/low_power.h b/src/rp2_common/pico_low_power/include/pico/low_power.h new file mode 100644 index 000000000..e5d87b3fe --- /dev/null +++ b/src/rp2_common/pico_low_power/include/pico/low_power.h @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_LOW_POWER_H_ +#define _PICO_LOW_POWER_H_ + +#include "pico.h" +#include "hardware/timer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file low_power.h + * \defgroup pico_low_power pico_low_power + * + * Lower Power APIs + * + * There are three modes of operation: sleep, dormant, and Pstate, with the lowest power consumption being Pstate. + * + * \if rp2040_specific + * NOTE: On RP2040, there is no Pstate mode. + * \endif + * + * In sleep mode: + * - Some clocks can be left running controlled by the SLEEP_EN registers in the clocks + * 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. + * - You can wake up from sleep by any interrupt, provided the clock for that interrupt is kept enabled + * - The sleep APIs will keep the clocks enabled for stdio during sleep, along with USB clocks when using tinyusb. + * + * In dormant mode: + * - Both xosc and rosc are stopped, until the dormant clock source is started again by an external event. + * - USB will be disabled before going into dormant, and re-enabled after waking up. + * - You can only wake up from dormant using the AON timer, or a GPIO interrupt configured using \ref gpio_set_dormant_irq_enabled. + * All other interrupts will be disabled during dormant. + * + * \if (!rp2040_specific) + * In Pstate mode: + * - Some power domains are switched off and don't retain state (eg specified SRAMs). By default, only the power domains + * required to keep persistent data powered on will be kept on. + * - You can only wake up from Pstate using the AON timer, or a GPIO wakeup configured using \ref powman_enable_gpio_wakeup. + * - Waking up from Pstate will run your program from the start, optionally executing a resume_func during runtime init. + * All non-persistent data will be overwritten by crt0 when the program starts again. + * - Variables can be marked as persistent using the __persistent_data macro. The location of the data can be set using the + * CMake function pico_set_persistent_data_loc. The persistent data can be stored in XIP_SRAM or main SRAM. + * - The Pstate APIs will overwrite the last 2 powman scratch registers - the other scratch registers are not modified, + * so can be used for other persistent data. + * \endif + * + * \subsection sleep_example Example + * \addtogroup pico_sleep + * \include hello_sleep.c + */ + +// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PICO_LOW_POWER, Enable/disable assertions in the pico_low_power module, type=bool, default=0, group=pico_low_power +#ifndef PARAM_ASSERTIONS_ENABLED_PICO_LOW_POWER +#define PARAM_ASSERTIONS_ENABLED_PICO_LOW_POWER 0 +#endif + +#include "hardware/clocks.h" +#if HAS_POWMAN_TIMER +#include "hardware/powman.h" +#endif + +typedef enum { + DORMANT_CLOCK_SOURCE_XOSC, + DORMANT_CLOCK_SOURCE_ROSC, +#if !PICO_RP2040 + DORMANT_CLOCK_SOURCE_LPOSC, +#endif + NUM_DORMANT_CLOCK_SOURCES +} dormant_clock_source_t; + +#if HAS_POWMAN_TIMER +typedef void (*low_power_pstate_resume_func)(pstate_bitset_t *pstate); +#endif + + +/*! \brief Sleep until an interrupt occurs + * \ingroup pico_low_power + * Sleep until any interrupt occurs. The clocks specified in keep_enabled will be kept enabled during sleep. + * + * \param keep_enabled The clocks to keep enabled during sleep. + * \return 0 on success, non-zero on error. + */ +int low_power_sleep_until_irq(const clock_dest_bitset_t *keep_enabled); + +/*! \brief Sleep until time using timer + * \ingroup pico_low_power + * Sleep until the given timer reaches the specified value. The clocks specified in keep_enabled will be kept enabled during sleep, along with clocks required + * for the timer. If exclusive is true, only the timer interrupt will be listened for, otherwise other interrupts will also be listened for. + * + * \param timer The timer to use. + * \param until The time to sleep until. + * \param keep_enabled The clocks to keep enabled during sleep. + * \param exclusive Whether to only listen for the timer interrupt, or other interrupts. + * \return 0 on success, non-zero on error. + */ +int low_power_sleep_until_timer(timer_hw_t *timer, absolute_time_t until, const clock_dest_bitset_t *keep_enabled, bool exclusive); + +/*! \brief Sleep until time using default timer + * \ingroup pico_low_power + * See \ref low_power_sleep_until_timer for more information. + * + * \param until The time to sleep until. + * \param keep_enabled The clocks to keep enabled during sleep. + * \param exclusive Whether to only listen for the timer interrupt, or other interrupts. + * \return 0 on success, non-zero on error. + */ +static inline int low_power_sleep_until_default_timer(absolute_time_t until, const clock_dest_bitset_t *keep_enabled, bool exclusive) { + return low_power_sleep_until_timer(PICO_DEFAULT_TIMER_INSTANCE(), until, keep_enabled, exclusive); +} + + +/*! \brief Sleep until pin state changes + * \ingroup pico_low_power + * Sleep until the given GPIO pin changes state. The clocks specified in keep_enabled will be kept enabled during sleep. + * If exclusive is true, only the GPIO interrupt will be listened for, otherwise other interrupts will also be listened for. + * + * \param gpio_pin The GPIO pin to use. + * \param edge Whether to listen for edge or level. + * \param high Whether to listen for high level / rising edge (true), or low level / falling edge (false). + * \param keep_enabled The clocks to keep enabled during sleep. + * \param exclusive Whether to only listen for the GPIO interrupt, or other interrupts. + * \return 0 on success, non-zero on error. + */ +int low_power_sleep_until_pin_state(uint gpio_pin, bool edge, bool high, const clock_dest_bitset_t *keep_enabled, bool exclusive); + +/*! \brief Go dormant until time using AON timer + * \ingroup pico_low_power + * Go dormant until the given AON timer reaches the specified value. + * The clocks specified in keep_enabled will be kept enabled during dormant, but XOSC and ROSC will be stopped. + * + * \if (!rp2040_specific) + * If the clock source is set to DORMANT_CLOCK_SOURCE_LPOSC, clk_sys will be switched to the ROSC while dormant so + * it can be stopped, while clk_ref will be run from the LPOSC so that it continues running for the timer. + * \endif + * + * \param until The time to go dormant until. + * \param dormant_clock_source The clock source to use for dormant. Must be DORMANT_CLOCK_SOURCE_LPOSC on RP2350. + * \param src_hz The frequency of the external RTC clock source on RP2040. Ignored on RP2350. + * \param gpio_pin The GPIO pin to use for the external RTC clock source on RP2040. Ignored on RP2350. + * \param keep_enabled The clocks to keep enabled during dormant. + * \return 0 on success, non-zero on error. + */ +int low_power_dormant_until_aon_timer(absolute_time_t until, dormant_clock_source_t dormant_clock_source, uint src_hz, uint gpio_pin, const clock_dest_bitset_t *keep_enabled); + +/*! \brief Go dormant until pin state changes + * \ingroup pico_low_power + * Go dormant until the given GPIO pin changes state. + * The clocks specified in keep_enabled will be kept enabled during dormant, but XOSC and ROSC will be stopped. + * + * \if (!rp2040_specific) + * If the clock source is set to DORMANT_CLOCK_SOURCE_LPOSC, clk_sys will be run from the ROSC while dormant so + * it can be stopped, while clk_ref will be run from the LPOSC so that continues running for the GPIO interrupt. + * \endif + * + * \param gpio_pin The GPIO pin to use. + * \param edge Whether to listen for edge or level. + * \param high Whether to listen for high level / rising edge (true), or low level / falling edge (false). + * \param dormant_clock_source The clock source to use for dormant. + * \param keep_enabled The clocks to keep enabled during dormant. + * \return 0 on success, non-zero on error. + */ +int low_power_dormant_until_pin_state(uint gpio_pin, bool edge, bool high, dormant_clock_source_t dormant_clock_source, const clock_dest_bitset_t *keep_enabled); + +#if HAS_POWMAN_TIMER +/*! \brief Go to Pstate until time using AON timer + * \ingroup pico_low_power + * Go to Pstate until the given AON timer reaches the specified value. The function specified in resume_func will be called on reboot, + * with the low power Pstate passed to it. + * + * If pstate is NULL, it will go to the minimum Pstate that will keep persistent data powered on. + * + * To also wake up from a GPIO, configure that using \ref powman_enable_gpio_wakeup before calling this function. + * + * NOTE: This function will overwrite the last 2 powman scratch registers - the other scratch registers are not modified. + * + * \param until The time to go to Pstate until. + * \param pstate The Pstate to use. If NULL, the Pstate will keep persistent data powered on. + * \param resume_func The function to call on reboot. + * \return 0 on success, non-zero on error. + */ +int low_power_pstate_until_aon_timer(absolute_time_t until, pstate_bitset_t *pstate, low_power_pstate_resume_func resume_func); + +/*! \brief Go to Pstate until pin state changes + * \ingroup pico_low_power + * Go to Pstate until the given GPIO pin changes state. The function specified in resume_func will be called on reboot, + * with the low power Pstate passed to it. + * + * If pstate is NULL, it will go to the minimum Pstate that will keep persistent data powered on. + * + * NOTE: This function will overwrite the last 2 powman scratch registers - the other scratch registers are not modified. + * + * \param gpio_pin The GPIO pin to use. + * \param edge Whether to listen for edge or level. + * \param high Whether to listen for the high/low level, or rising/falling edge. + * \param pstate The Pstate to use. If NULL, the Pstate will keep persistent data powered on. + * \param resume_func The function to call on reboot. + * \return 0 on success, non-zero on error. + */ +int low_power_pstate_until_pin_state(uint gpio_pin, bool edge, bool high, pstate_bitset_t *pstate, low_power_pstate_resume_func resume_func); + +/*! \brief Get Pstate which keeps persistent data powered on + * \ingroup pico_low_power + * + * \param pstate Pointer to the Pstate to write the result to. + * \return The Pstate. + */ +pstate_bitset_t *low_power_persistent_pstate_get(pstate_bitset_t *pstate); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/rp2_common/pico_low_power/low_power.c b/src/rp2_common/pico_low_power/low_power.c new file mode 100644 index 000000000..edfa19203 --- /dev/null +++ b/src/rp2_common/pico_low_power/low_power.c @@ -0,0 +1,719 @@ +/* + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico.h" + +#include "pico/low_power.h" +#include "pico/aon_timer.h" +#include "pico/runtime_init.h" +#include "pico/time.h" +#include "pico/stdio.h" +#if LIB_PICO_STDIO_USB +#include "pico/stdio_usb.h" +#endif + +#include "hardware/pll.h" +#include "hardware/claim.h" +#include "hardware/clocks.h" +#include "hardware/gpio.h" +#include "hardware/rosc.h" +#include "hardware/sync.h" +#include "hardware/timer.h" +#include "hardware/uart.h" +#include "hardware/watchdog.h" +#include "hardware/xosc.h" + +#if LIB_TINYUSB_DEVICE || LIB_TINYUSB_HOST +#include "tusb.h" +#endif + +#if HAS_RP2040_RTC +#include "hardware/rtc.h" +#elif HAS_POWMAN_TIMER +#include "hardware/powman.h" +#endif + +// For __wfi +#ifdef __riscv +#include "hardware/riscv.h" +#else +// For scb_hw so we can enable deep sleep +#include "hardware/structs/scb.h" +#endif + +// ------------------------------------------------------------------------------------------------------ +// todo these probably belong in h/w clocks as some sort of registered thing, but leave them private here +// for now +static void prepare_for_clock_gating(void) { + // particularly for UART we want nothing left to clock out + stdio_flush(); +} + +static void post_clock_gating(void) { + // restore all clocks in sleep mode, to prevent other __wfi from causing issues + clock_dest_bitset_t all = clock_dest_bitset_all(); + clock_gate_sleep_en(&all); +} + +static uint32_t interrupt_flags; + +#if LIB_TINYUSB_DEVICE +static bool tud_was_inited = false; +#endif + +#if LIB_TINYUSB_HOST +static bool tuh_was_inited = false; +#endif + +static void prepare_for_clock_switch(void) { + // particularly for UART we want nothing left to clock out + prepare_for_clock_gating(); + +#if LIB_TINYUSB_DEVICE + tud_was_inited = tud_inited(); + if (tud_was_inited) tud_deinit(0); +#endif + +#if LIB_TINYUSB_HOST + tuh_was_inited = tuh_inited(); + if (tuh_was_inited) tuh_deinit(0); +#endif + +#if LIB_PICO_STDIO_USB + // deinit USB + stdio_usb_deinit(); +#endif + + // disable interrupts + interrupt_flags = save_and_disable_interrupts(); +} + +static void post_clock_switch(void) { + // restore interrupts + restore_interrupts_from_disabled(interrupt_flags); + +#if LIB_TINYUSB_DEVICE + if (tud_was_inited) tud_init(0); +#endif + +#if LIB_TINYUSB_HOST + if (tuh_was_inited) tuh_init(0); +#endif + +#if LIB_PICO_STDIO_USB + // reinit USB + stdio_usb_init(); +#endif +} + +#if HAS_POWMAN_TIMER +static void prepare_for_pstate_change(void) { + prepare_for_clock_switch(); +} + +static void post_pstate_change(void) { +} +#endif + +void low_power_enable_processor_deep_sleep(void) { + // Enable deep sleep at the proc +#ifdef __riscv + uint32_t bits = RVCSR_MSLEEP_POWERDOWN_BITS; + if (!get_core_num()) { + // see errata RP2350-E4 + bits |= RVCSR_MSLEEP_DEEPSLEEP_BITS; + } + riscv_set_csr(RVCSR_MSLEEP_OFFSET, bits); +#else + scb_hw->scr |= ARM_CPU_PREFIXED(SCR_SLEEPDEEP_BITS); +#endif +} + +void low_power_disable_processor_deep_sleep(void) { +#ifdef __riscv + riscv_clear_csr(RVCSR_MSLEEP_OFFSET, RVCSR_MSLEEP_POWERDOWN_BITS | RVCSR_MSLEEP_DEEPSLEEP_BITS); +#else + scb_hw->scr &= ~ARM_CPU_PREFIXED(SCR_SLEEPDEEP_BITS); +#endif +} + +volatile bool event_happened; + +static void low_power_wakeup(void) { + event_happened = true; +} + +static void low_power_wakeup_gpio(__unused uint gpio, __unused uint32_t event_mask) { + low_power_wakeup(); +} + +static void replace_null_enable_values(const clock_dest_bitset_t *keep_enabled, + clock_dest_bitset_t *local_keep_enabled) { + if (keep_enabled) { + *local_keep_enabled = *keep_enabled; + } else { + // default to keep nothing on + *local_keep_enabled = clock_dest_bitset_none(); + } +} + +static void add_library_clocks(clock_dest_bitset_t *local_keep_enabled) { +#if LIB_PICO_STDIO_USB || LIB_TINYUSB_HOST || LIB_TINYUSB_DEVICE + // this is necessary to prevent dropping the connection + #if PICO_RP2040 + clock_dest_bitset_add(local_keep_enabled, CLK_DEST_SYS_USBCTRL); + clock_dest_bitset_add(local_keep_enabled, CLK_DEST_USB_USBCTRL); + #elif PICO_RP2350 + clock_dest_bitset_add(local_keep_enabled, CLK_DEST_SYS_USBCTRL); + clock_dest_bitset_add(local_keep_enabled, CLK_DEST_USB); + #else + #error Unknown processor + #endif +#endif + +#if LIB_PICO_STDIO_UART + // this is only needed to prevent losing stdin while sleeping + clock_dest_bitset_add(local_keep_enabled, PICO_DEFAULT_UART ? CLK_DEST_PERI_UART1 : CLK_DEST_PERI_UART0); + clock_dest_bitset_add(local_keep_enabled, PICO_DEFAULT_UART ? CLK_DEST_SYS_UART1 : CLK_DEST_SYS_UART0); +#endif +} + +// Ceiling division of NUM_IRQS by 32 +#define NUM_IRQ_WORDS ((NUM_IRQS / 32) + ((NUM_IRQS % 32) > 0)) + +static uint32_t irq_mask_disabled_during_sleep[NUM_IRQ_WORDS]; + +static void save_and_disable_other_interrupts(uint32_t irq) { + for (uint n = 0; n < NUM_IRQ_WORDS; n++) { + irq_mask_disabled_during_sleep[n] = irq_get_mask_n(n); + if (irq >= n * 32 && irq < (n + 1) * 32) { + irq_mask_disabled_during_sleep[n] &= ~(1u << (irq % 32)); + } + irq_set_mask_n_enabled(n, irq_mask_disabled_during_sleep[n], false); + } +} + +static void restore_other_interrupts(void) { + for (uint n = 0; n < NUM_IRQ_WORDS; n++) { + irq_set_mask_n_enabled(n, irq_mask_disabled_during_sleep[n], true); + } +} + +int low_power_sleep_until_irq(const clock_dest_bitset_t *keep_enabled) { + clock_dest_bitset_t local_keep_enabled; + replace_null_enable_values(keep_enabled, &local_keep_enabled); + + add_library_clocks(&local_keep_enabled); + + prepare_for_clock_gating(); + // gate clocks + clock_gate_sleep_en(&local_keep_enabled); + + low_power_enable_processor_deep_sleep(); + // Go to sleep until any event happens + __wfi(); + low_power_disable_processor_deep_sleep(); + + post_clock_gating(); + + return 0; +} + +// only the deep_sleep variant of this, as DORMANT cannot wake from TIMER +int low_power_sleep_until_timer(timer_hw_t *timer, absolute_time_t until, + const clock_dest_bitset_t *keep_enabled, bool exclusive) { + int alarm_num = timer_hardware_alarm_claim_unused(timer, false); + if (alarm_num < 0) return PICO_ERROR_INSUFFICIENT_RESOURCES; + + event_happened = false; + timer_hardware_alarm_set_callback(timer, alarm_num, ((hardware_alarm_callback_t )low_power_wakeup)); + if (timer_hardware_alarm_set_target(timer, alarm_num, until)) { + timer_hardware_alarm_unclaim(timer, alarm_num); + // the time has passed already + return 0; + } + + clock_dest_bitset_t local_keep_enabled; + replace_null_enable_values(keep_enabled, &local_keep_enabled); +#if PICO_RP2040 + clock_dest_bitset_add(&local_keep_enabled, CLK_DEST_SYS_TIMER); +#elif PICO_RP2350 + clock_dest_bitset_add(&local_keep_enabled, timer_get_index(timer) ? CLK_DEST_SYS_TIMER1 : CLK_DEST_SYS_TIMER0); + clock_dest_bitset_add(&local_keep_enabled, CLK_DEST_REF_TICKS); +#else +#error Unknown processor +#endif + + add_library_clocks(&local_keep_enabled); + +#if NUM_GENERIC_TIMERS == 1 +#define TIMER_BASE_IRQ TIMER_IRQ_0 +#else +#define TIMER_BASE_IRQ TIMER0_IRQ_0 +#endif + + if (exclusive) save_and_disable_other_interrupts(TIMER_BASE_IRQ + alarm_num + (timer_get_index(timer) * NUM_ALARMS)); + + prepare_for_clock_gating(); + // gate clocks + clock_gate_sleep_en(&local_keep_enabled); + + low_power_enable_processor_deep_sleep(); + // Go to sleep until the wakeup event happens (note it may have happened already) + while (!event_happened) __wfi(); + low_power_disable_processor_deep_sleep(); + + timer_hardware_alarm_set_callback(timer, alarm_num, NULL); + timer_hardware_alarm_unclaim(timer, alarm_num); + + post_clock_gating(); + + if (exclusive) restore_other_interrupts(); + + return 0; +} + +int low_power_sleep_until_pin_state(uint gpio_pin, bool edge, bool high, + const clock_dest_bitset_t *keep_enabled, bool exclusive) { + + event_happened = false; + + clock_dest_bitset_t local_keep_enabled; + replace_null_enable_values(keep_enabled, &local_keep_enabled); + + add_library_clocks(&local_keep_enabled); + + // Configure the appropriate IRQ at IO bank 0 + assert(gpio_pin < NUM_BANK0_GPIOS); + + uint32_t event = 0; + + if (edge) { + event = high ? GPIO_IRQ_EDGE_RISE : GPIO_IRQ_EDGE_FALL; + } else { // level + event = high ? GPIO_IRQ_LEVEL_HIGH : GPIO_IRQ_LEVEL_LOW; + } + + gpio_set_input_enabled(gpio_pin, true); + gpio_set_irq_enabled_with_callback(gpio_pin, event, true, low_power_wakeup_gpio); + + if (exclusive) save_and_disable_other_interrupts(IO_IRQ_BANK0); + + prepare_for_clock_gating(); + // gate clocks + clock_gate_sleep_en(&local_keep_enabled); + + low_power_enable_processor_deep_sleep(); + // Go to sleep until the wakeup event happens (note it may have happened already) + while (!event_happened) __wfi(); + low_power_disable_processor_deep_sleep(); + + // Clear the irq so we can go back to dormant mode again if we want + gpio_acknowledge_irq(gpio_pin, event); + gpio_set_irq_enabled_with_callback(gpio_pin, event, false, NULL); + + post_clock_gating(); + + if (exclusive) restore_other_interrupts(); + + return 0; +} + +// In order to go into dormant mode we need to be running from a stoppable clock source: +// either the xosc or rosc with no PLLs running. This means we disable the USB and ADC clocks +// and all PLLs +void low_power_setup_clocks_for_dormant(dormant_clock_source_t dormant_source) { + prepare_for_clock_switch(); + + uint clk_ref_src_hz; + uint32_t clk_ref_src; + uint clk_sys_src_hz; + uint32_t clk_sys_src; + uint32_t clk_sys_aux_src; + switch (dormant_source) { + case DORMANT_CLOCK_SOURCE_XOSC: + clk_ref_src_hz = XOSC_HZ; + clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC; + clk_sys_src_hz = clk_ref_src_hz; + clk_sys_src = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF; + clk_sys_aux_src = 0; + break; + case DORMANT_CLOCK_SOURCE_ROSC: + clk_ref_src_hz = rosc_measure_freq_khz() * KHZ; + clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; + clk_sys_src_hz = clk_ref_src_hz; + clk_sys_src = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF; + clk_sys_aux_src = 0; + break; +#if !PICO_RP2040 + case DORMANT_CLOCK_SOURCE_LPOSC: + clk_ref_src_hz = 32 * KHZ; + clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_LPOSC_CLKSRC; + clk_sys_src_hz = rosc_measure_freq_khz() * KHZ; + clk_sys_src = CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX; + clk_sys_aux_src = CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_ROSC_CLKSRC; + break; +#endif + default: + hard_assert(false); + __builtin_unreachable(); + } + + clock_configure_undivided(clk_ref, + clk_ref_src, + 0, + clk_ref_src_hz); + + // CLK SYS = CLK_REF + clock_configure_undivided(clk_sys, + clk_sys_src, + clk_sys_aux_src, + clk_sys_src_hz); + + + // CLK ADC = 0MHz + clock_stop(clk_adc); + clock_stop(clk_usb); +#if HAS_HSTX + clock_stop(clk_hstx); +#endif + +#if HAS_RP2040_RTC + // CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc + uint clk_rtc_src = (dormant_source == DORMANT_CLOCK_SOURCE_XOSC) ? + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; + + clock_configure(clk_rtc, + 0, // No GLMUX + clk_rtc_src, + clk_sys_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, + 0, + CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, + clk_sys_src_hz, + clk_sys_src_hz); + + pll_deinit(pll_sys); + pll_deinit(pll_usb); + + // Assuming both xosc and rosc are running at the moment + if (dormant_source == DORMANT_CLOCK_SOURCE_XOSC) { + // Safe to disable rosc + rosc_disable(); + } else { + // Safe to disable xosc + xosc_disable(); + } +} + +//To be called after waking up from sleep/dormant mode to restore system clocks properly +void low_power_wake_from_dormant(void) { + //Re-enable the ring oscillator, which will essentially kickstart the proc + rosc_restart(); + + post_clock_gating(); + + //Restore all inactive clocks + runtime_init_clocks(); + post_clock_switch(); +} + +void low_power_go_dormant(dormant_clock_source_t dormant_clock_source) { + assert( + dormant_clock_source == DORMANT_CLOCK_SOURCE_XOSC || dormant_clock_source == DORMANT_CLOCK_SOURCE_ROSC + #if !PICO_RP2040 + || dormant_clock_source == DORMANT_CLOCK_SOURCE_LPOSC + #endif + ); + + if (dormant_clock_source == DORMANT_CLOCK_SOURCE_XOSC) { + xosc_dormant(); + } else { + rosc_set_dormant(); + } +} + +int low_power_dormant_until_aon_timer(absolute_time_t until, + dormant_clock_source_t dormant_clock_source, + uint src_hz, uint gpio_pin, + const clock_dest_bitset_t *keep_enabled) { + low_power_setup_clocks_for_dormant(dormant_clock_source); + + clock_dest_bitset_t local_keep_enabled; + replace_null_enable_values(keep_enabled, &local_keep_enabled); + +#if PICO_RP2040 + // The RTC must be run from an external source, since the dormant source will be inactive + rtc_run_from_external_source(src_hz, gpio_pin); + clock_dest_bitset_add(&local_keep_enabled, CLK_DEST_RTC_RTC); +#elif PICO_RP2350 + ((void)src_hz); + ((void)gpio_pin); + if (dormant_clock_source == DORMANT_CLOCK_SOURCE_LPOSC) + powman_timer_set_1khz_tick_source_lposc(); + else + return PICO_ERROR_INVALID_ARG; + + clock_dest_bitset_add(&local_keep_enabled, CLK_DEST_REF_POWMAN); +#else + #error Unknown processor +#endif + + // todo catch race condition here (or just plain in the past) + struct timespec ts; + us_to_timespec(to_us_since_boot(until), &ts); + event_happened = false; + aon_timer_enable_alarm(&ts, (aon_timer_alarm_handler_t)low_power_wakeup, true); + + prepare_for_clock_gating(); + // gate clocks + clock_gate_sleep_en(&local_keep_enabled); + + low_power_enable_processor_deep_sleep(); + + //Go dormant + low_power_go_dormant(dormant_clock_source); + + + assert(event_happened); + low_power_wake_from_dormant(); + + return 0; +} + +int low_power_dormant_until_pin_state(uint gpio_pin, bool edge, bool high, + dormant_clock_source_t dormant_clock_source, + const clock_dest_bitset_t *keep_enabled) { + + low_power_setup_clocks_for_dormant(dormant_clock_source); + + clock_dest_bitset_t local_keep_enabled; + replace_null_enable_values(keep_enabled, &local_keep_enabled); + + // Configure the appropriate IRQ at IO bank 0 + assert(gpio_pin < NUM_BANK0_GPIOS); + + uint32_t event = 0; + + if (edge) { + event = high ? GPIO_IRQ_EDGE_RISE : GPIO_IRQ_EDGE_FALL; + } else { // level + event = high ? GPIO_IRQ_LEVEL_HIGH : GPIO_IRQ_LEVEL_LOW; + } + + gpio_set_input_enabled(gpio_pin, true); + gpio_set_dormant_irq_enabled(gpio_pin, event, true); + + prepare_for_clock_gating(); + // gate clocks + clock_gate_sleep_en(&local_keep_enabled); + + low_power_enable_processor_deep_sleep(); + + //Go dormant + low_power_go_dormant(dormant_clock_source); + + // Clear the irq so we can go back to dormant mode again if we want + gpio_acknowledge_irq(gpio_pin, event); + gpio_set_dormant_irq_enabled(gpio_pin, event, false); + + low_power_wake_from_dormant(); + + return 0; +} + +#if HAS_POWMAN_TIMER +extern unsigned char __persistent_data_start__[]; +extern unsigned char __persistent_data_end__[]; + +int low_power_pstate_set(pstate_bitset_t *pstate) { + invalid_params_if(PICO_LOW_POWER, !pstate_bitset_is_set(pstate, POWMAN_POWER_DOMAIN_SWITCHED_CORE)); + + return powman_set_power_state(pstate_bitset_to_powman_power_state(pstate)); +} + +pstate_bitset_t *low_power_pstate_get(pstate_bitset_t *pstate) { + pstate_bitset_from_powman_power_state(pstate, powman_get_power_state()); + return pstate; +} + +pstate_bitset_t *low_power_persistent_pstate_get(pstate_bitset_t *pstate) { + pstate_bitset_clear(pstate); + + if ((uint32_t)__persistent_data_start__ == (uint32_t)__persistent_data_end__) { + // No persistent data, so power down everything + return pstate; + } + + // Keep __persistent_data_start__ on + if ((uint32_t)__persistent_data_start__ < SRAM_BASE) { + pstate_bitset_add(pstate, POWMAN_POWER_DOMAIN_XIP_CACHE); + } else if ((uint32_t)__persistent_data_start__ < SRAM4_BASE) { + pstate_bitset_add(pstate, POWMAN_POWER_DOMAIN_SRAM_BANK0); + + // Keep __persistent_data_end__ on too, if it is in SRAM bank 1 + if ((uint32_t)__persistent_data_end__ >= SRAM4_BASE) { + pstate_bitset_add(pstate, POWMAN_POWER_DOMAIN_SRAM_BANK1); + } + } else { + pstate_bitset_add(pstate, POWMAN_POWER_DOMAIN_SRAM_BANK1); + } + + return pstate; +} + +int low_power_go_pstate(pstate_bitset_t *pstate, low_power_pstate_resume_func resume_func) { + pstate_bitset_t default_pstate = pstate_bitset_none(); + if (pstate == NULL) { + pstate = &default_pstate; + low_power_persistent_pstate_get(pstate); + } + + prepare_for_pstate_change(); + + // Configure the wakeup state + pstate_bitset_t current_pstate = pstate_bitset_none(); + low_power_pstate_get(¤t_pstate); + bool valid_state = powman_configure_wakeup_state(pstate_bitset_to_powman_power_state(pstate), pstate_bitset_to_powman_power_state(¤t_pstate)); + if (!valid_state) { + return PICO_ERROR_INVALID_STATE; + } + + // reboot to main + powman_hw->boot[0] = 0; + powman_hw->boot[1] = 0; + powman_hw->boot[2] = 0; + powman_hw->boot[3] = 0; + + // Store the low power state and resume function for use after reboot + powman_hw->scratch[6] = pstate_bitset_to_powman_power_state(pstate); + powman_hw->scratch[7] = (uint32_t)resume_func; + + // Switch to required power state + int rc = powman_set_power_state(pstate_bitset_to_powman_power_state(pstate)); + if (rc != PICO_OK) { + return rc; + } + + // Power down + while (true) __wfi(); + + // Should not reach here + post_pstate_change(); + + return rc; +} + +int low_power_pstate_until_aon_timer(absolute_time_t until, pstate_bitset_t *pstate, low_power_pstate_resume_func resume_func) { + powman_enable_alarm_wakeup_at_ms(to_ms_since_boot(until)); + + return low_power_go_pstate(pstate, resume_func); +} + +int low_power_pstate_until_pin_state(uint gpio_pin, bool edge, bool high, pstate_bitset_t *pstate, low_power_pstate_resume_func resume_func) { + powman_enable_gpio_wakeup(0, gpio_pin, edge, high); + + return low_power_go_pstate(pstate, resume_func); +} + +#if !PICO_RUNTIME_NO_INIT_LOW_POWER_REBOOT_CHECK +void __weak runtime_init_low_power_reboot_check(void) { + // check if we came from powman reboot + if (powman_hw->chip_reset & POWMAN_CHIP_RESET_HAD_SWCORE_PD_BITS) { + // we came from powman reboot, so execute the resume function + if (powman_hw->scratch[7]) { + pstate_bitset_t pstate = pstate_bitset_none(); + pstate_bitset_from_powman_power_state(&pstate, powman_hw->scratch[6]); + ((low_power_pstate_resume_func)powman_hw->scratch[7])(&pstate); + // clear the scratch registers + powman_hw->scratch[6] = 0; + powman_hw->scratch[7] = 0; + } + } +} +#endif + +#if !PICO_RUNTIME_SKIP_INIT_LOW_POWER_REBOOT_CHECK +PICO_RUNTIME_INIT_FUNC_RUNTIME(runtime_init_low_power_reboot_check, PICO_RUNTIME_INIT_LOW_POWER_REBOOT_CHECK); +#endif + +#if !PICO_RUNTIME_NO_INIT_LOW_POWER_CACHE_UNPIN +void __weak __no_inline_not_in_flash_func(runtime_init_low_power_cache_unpin)(void) { + // if persistent data is in xip_sram, then the whole cache is currently pinned + // for performance, we should unpin the rest of it + if ((uint32_t)__persistent_data_start__ < SRAM_BASE) { + uint32_t persistent_data_start_maintenance = XIP_MAINTENANCE_BASE + ((uint32_t)__persistent_data_start__ - XIP_BASE); + uint32_t persistent_data_end_maintenance = XIP_MAINTENANCE_BASE + ((uint32_t)__persistent_data_end__ - XIP_BASE); + volatile uint8_t* cache; + for ( + cache = (volatile uint8_t*)(XIP_MAINTENANCE_BASE + XIP_SRAM_BASE - XIP_BASE); + cache < (volatile uint8_t*)(XIP_MAINTENANCE_BASE + XIP_END - XIP_BASE); + cache += 8 + ) { + if ((uint32_t)cache >= persistent_data_start_maintenance && (uint32_t)cache < persistent_data_end_maintenance) { + continue; + } + *(cache + 0) = 0; // invalidate + } + } +} +#endif + +#if !PICO_RUNTIME_SKIP_INIT_LOW_POWER_CACHE_UNPIN +PICO_RUNTIME_INIT_FUNC_RUNTIME(runtime_init_low_power_cache_unpin, PICO_RUNTIME_INIT_LOW_POWER_CACHE_UNPIN); +#endif + +#endif // HAS_POWMAN_TIMER + +#if !PICO_RUNTIME_NO_INIT_RP2350_SLEEP_FIX +#include "hardware/sync.h" +void __weak __not_in_flash_func(runtime_init_rp2350_sleep_fix)(void) { + if (watchdog_hw->reason && WATCHDOG_REASON_TIMER_BITS) { // detect rom_reboot() usage + uint32_t flags = save_and_disable_interrupts(); + + // Clear (and save) NVIC mask so only the dummy can fire + uint32_t saved_irq_mask[NUM_IRQ_WORDS]; + for (uint i = 0; i < NUM_IRQ_WORDS; ++i) { + saved_irq_mask[i] = nvic_hw->icer[i]; + nvic_hw->icer[i] = -1u; + } + + // Un-pend then enable the dummy + const uint32_t dummy_irq_idx = FIRST_USER_IRQ / 32u; + const uint32_t dummy_irq_bit = FIRST_USER_IRQ % 32u; + nvic_hw->icpr[dummy_irq_idx] = 1u << dummy_irq_bit; + nvic_hw->iser[dummy_irq_idx] = 1u << dummy_irq_bit; + + // Sleep and immediately dummy-IRQ back out of sleep (these events happen + // in reverse order on M33; Armv8-M doesn't specify the ordering) + pico_default_asm_volatile ( + ".p2align 2\n" // Make sure both 16-bit instructions are fetched + "str %0, [%1]\n" + "wfi\n" + : + : "l" (1u << dummy_irq_bit), + "l" (&nvic_hw->ispr[dummy_irq_idx]) + ); + + // Restore NVIC mask + nvic_hw->icer[dummy_irq_idx] = 1u << dummy_irq_bit; + for (uint i = 0; i < NUM_IRQ_WORDS; ++i) { + nvic_hw->iser[i] = saved_irq_mask[i]; + } + + restore_interrupts(flags); + } +} +#endif + +#if !PICO_RUNTIME_SKIP_INIT_RP2350_SLEEP_FIX +PICO_RUNTIME_INIT_FUNC_RUNTIME(runtime_init_rp2350_sleep_fix, PICO_RUNTIME_INIT_RP2350_SLEEP_FIX); +#endif diff --git a/src/rp2_common/pico_platform_sections/include/pico/platform/sections.h b/src/rp2_common/pico_platform_sections/include/pico/platform/sections.h index e85700295..591e9e3b2 100644 --- a/src/rp2_common/pico_platform_sections/include/pico/platform/sections.h +++ b/src/rp2_common/pico_platform_sections/include/pico/platform/sections.h @@ -98,6 +98,32 @@ #define __uninitialized_ram(group) __attribute__((section(".uninitialized_data." #group))) group #endif +#if LIB_PICO_LOW_POWER && HAS_POWMAN_TIMER +/*! \brief Section attribute macro for placement in a section persisted across default POWMAN resets + * \ingroup pico_platform + * + * Data marked this way will retain its value across a default POWMAN reset, and will be zeroed on + * any other reset. + * + * For example a `uint32_t` foo that will be zeroed initially, then retain its value if the program + * is restarted by default POWMAN reset. + * + * uint32_t __persistent_data(foo); + * + * The section attribute is `.persistent_data.` + * + * \param name the name of the variable to place in the section + */ +#ifndef __persistent_data +#define __persistent_data(name) __attribute__((section(".persistent_data." #name))) name +#endif +#else +// If not supported, use the .bss section, as that will be zeroed on boot +#ifndef __persistent_data +#define __persistent_data(name) __attribute__((section(".bss." #name))) name +#endif +#endif + /*! \brief Section attribute macro for placement in flash even in a COPY_TO_RAM binary * \ingroup pico_platform * diff --git a/src/rp2_common/pico_runtime_init/include/pico/runtime_init.h b/src/rp2_common/pico_runtime_init/include/pico/runtime_init.h index c6ed4cf8e..bcf10dce5 100644 --- a/src/rp2_common/pico_runtime_init/include/pico/runtime_init.h +++ b/src/rp2_common/pico_runtime_init/include/pico/runtime_init.h @@ -416,6 +416,60 @@ void runtime_init_bootrom_locking_enable(void); #endif #endif +// ------------------------------------------------------------ +// RP2350 sleep fix +// ------------------------------------------------------------ + +#ifndef PICO_RUNTIME_INIT_RP2350_SLEEP_FIX +#define PICO_RUNTIME_INIT_RP2350_SLEEP_FIX "00070" +#endif + +#ifndef PICO_RUNTIME_SKIP_INIT_RP2350_SLEEP_FIX +#if PICO_RP2350 && !defined(__riscv) +#define PICO_RUNTIME_SKIP_INIT_RP2350_SLEEP_FIX 0 +#else +#define PICO_RUNTIME_SKIP_INIT_RP2350_SLEEP_FIX 1 +#endif +#endif + +#ifndef PICO_RUNTIME_NO_INIT_RP2350_SLEEP_FIX +#if PICO_RP2350 && !defined(__riscv) +#define PICO_RUNTIME_NO_INIT_RP2350_SLEEP_FIX 0 +#else +#define PICO_RUNTIME_NO_INIT_RP2350_SLEEP_FIX 1 +#endif +#endif + +// ------------------------------------------------------------ +// Low power initialization +// ------------------------------------------------------------ + +// Unpin cache if persistent data is in xip_sram - do this early for performance +#ifndef PICO_RUNTIME_INIT_LOW_POWER_CACHE_UNPIN +#define PICO_RUNTIME_INIT_LOW_POWER_CACHE_UNPIN "00650" +#endif + +#ifndef PICO_RUNTIME_SKIP_INIT_LOW_POWER_CACHE_UNPIN +#define PICO_RUNTIME_SKIP_INIT_LOW_POWER_CACHE_UNPIN !HAS_POWMAN_TIMER || PICO_NO_FLASH || !PICO_CRT0_PIN_XIP_SRAM +#endif + +#ifndef PICO_RUNTIME_NO_INIT_LOW_POWER_CACHE_UNPIN +#define PICO_RUNTIME_NO_INIT_LOW_POWER_CACHE_UNPIN !HAS_POWMAN_TIMER || PICO_NO_FLASH || !PICO_CRT0_PIN_XIP_SRAM +#endif + +// Run user callback if this is a powman reboot - do this later, so user has a full SDK to work with +#ifndef PICO_RUNTIME_INIT_LOW_POWER_REBOOT_CHECK +#define PICO_RUNTIME_INIT_LOW_POWER_REBOOT_CHECK "11020" +#endif + +#ifndef PICO_RUNTIME_SKIP_INIT_LOW_POWER_REBOOT_CHECK +#define PICO_RUNTIME_SKIP_INIT_LOW_POWER_REBOOT_CHECK !HAS_POWMAN_TIMER +#endif + +#ifndef PICO_RUNTIME_NO_INIT_LOW_POWER_REBOOT_CHECK +#define PICO_RUNTIME_NO_INIT_LOW_POWER_REBOOT_CHECK !HAS_POWMAN_TIMER +#endif + // ------------------------------------------------------------------------------------------------ // stack guard; these are a special case as they take a parameter; however the normal defines apply // ------------------------------------------------------------------------------------------------ diff --git a/src/rp2_common/pico_standard_link/script_include/section_persistent_data.incl b/src/rp2_common/pico_standard_link/script_include/section_persistent_data.incl new file mode 100644 index 000000000..33b449df7 --- /dev/null +++ b/src/rp2_common/pico_standard_link/script_include/section_persistent_data.incl @@ -0,0 +1,9 @@ +SECTIONS +{ + .persistent_data DEFINED(PERSISTENT_DATA_LOC) ? PERSISTENT_DATA_LOC : . (NOLOAD) : { + __persistent_data_start__ = .; + *(.persistent_data*) + . = ALIGN(4); + } + PROVIDE(__persistent_data_end__ = .); +} \ No newline at end of file diff --git a/src/rp2_common/pico_standard_link/script_include/sections_copy_to_ram_data.incl b/src/rp2_common/pico_standard_link/script_include/sections_copy_to_ram_data.incl index ff90005e1..c8940112f 100644 --- a/src/rp2_common/pico_standard_link/script_include/sections_copy_to_ram_data.incl +++ b/src/rp2_common/pico_standard_link/script_include/sections_copy_to_ram_data.incl @@ -2,3 +2,4 @@ INCLUDE "section_copy_to_ram_data.incl" INCLUDE "section_bss.incl" +INCLUDE "section_persistent_data.incl" diff --git a/src/rp2_common/pico_standard_link/script_include/sections_default_data.incl b/src/rp2_common/pico_standard_link/script_include/sections_default_data.incl index cfa77cb2f..6af190cc6 100644 --- a/src/rp2_common/pico_standard_link/script_include/sections_default_data.incl +++ b/src/rp2_common/pico_standard_link/script_include/sections_default_data.incl @@ -4,3 +4,4 @@ INCLUDE "section_ram_vector_table.incl" INCLUDE "section_uninitialized_data.incl" INCLUDE "section_default_data.incl" INCLUDE "section_bss.incl" +INCLUDE "section_persistent_data.incl" diff --git a/src/rp2_common/pico_standard_link/script_include/sections_no_flash_data.incl b/src/rp2_common/pico_standard_link/script_include/sections_no_flash_data.incl index 6a55a1481..037cd3f64 100644 --- a/src/rp2_common/pico_standard_link/script_include/sections_no_flash_data.incl +++ b/src/rp2_common/pico_standard_link/script_include/sections_no_flash_data.incl @@ -3,3 +3,4 @@ INCLUDE "section_no_flash_data.incl" INCLUDE "section_uninitialized_data.incl" INCLUDE "section_bss.incl" +INCLUDE "section_persistent_data.incl" diff --git a/src/rp2_common/pico_stdio_usb/stdio_usb.c b/src/rp2_common/pico_stdio_usb/stdio_usb.c index bbff5f612..e2b74a45f 100644 --- a/src/rp2_common/pico_stdio_usb/stdio_usb.c +++ b/src/rp2_common/pico_stdio_usb/stdio_usb.c @@ -261,8 +261,6 @@ bool stdio_usb_deinit(void) { return false; } - assert(tud_inited()); // we expect the caller to have initialized when calling sdio_usb_init - bool rc = true; stdio_set_driver_enabled(&stdio_usb, false); @@ -271,6 +269,13 @@ bool stdio_usb_deinit(void) { sleep_ms(PICO_STDIO_USB_DEINIT_DELAY_MS); #endif +#if PICO_STDIO_USB_ENABLE_TINYUSB_INIT + // deinitialize TinyUSB + tud_deinit(0); +#else + assert(!tud_inited()); // we expect the caller to have deinitialized if they are using TinyUSB +#endif + #if PICO_STDIO_USB_ENABLE_IRQ_BACKGROUND_TASK if (irq_has_shared_handler(USBCTRL_IRQ)) { spin_lock_unclaim(spin_lock_get_num(one_shot_timer_crit_sec.spin_lock)); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6e646f847..3cef961d3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,5 +14,6 @@ if (PICO_ON_DEVICE) add_subdirectory(cmsis_test) add_subdirectory(pico_sem_test) add_subdirectory(pico_sha256_test) + add_subdirectory(hello_sleep) add_subdirectory(pico_async_context_test) endif() diff --git a/test/hello_sleep/BUILD.bazel b/test/hello_sleep/BUILD.bazel new file mode 100644 index 000000000..2c007c6bc --- /dev/null +++ b/test/hello_sleep/BUILD.bazel @@ -0,0 +1,38 @@ +load("//bazel:defs.bzl", "compatible_with_rp2") + +package(default_visibility = ["//visibility:public"]) + +cc_binary( + name = "hello_sleep", + testonly = True, + srcs = ["hello_sleep.c"], + target_compatible_with = compatible_with_rp2(), + deps = [ + "//src/rp2_common/pico_stdlib", + "//src/rp2_common/pico_low_power", + "//src/rp2_common/pico_status_led", + ], +) + +cc_binary( + name = "hello_sleep_gpio", + testonly = True, + srcs = ["hello_sleep_gpio.c"], + target_compatible_with = compatible_with_rp2(), + deps = [ + "//src/rp2_common/pico_stdlib", + "//src/rp2_common/pico_low_power", + "//src/rp2_common/pico_status_led", + ], +) + +cc_binary( + name = "rtc_clksrc", + testonly = True, + srcs = ["rtc_clksrc.c"], + target_compatible_with = ["//bazel/constraint:rp2040"], + deps = [ + "//src/rp2_common/pico_stdlib", + "//src/rp2_common/pico_status_led", + ], +) diff --git a/test/hello_sleep/CMakeLists.txt b/test/hello_sleep/CMakeLists.txt new file mode 100644 index 000000000..093ec60a3 --- /dev/null +++ b/test/hello_sleep/CMakeLists.txt @@ -0,0 +1,121 @@ +add_executable(hello_sleep + hello_sleep.c + ) + +target_link_libraries(hello_sleep pico_stdlib pico_low_power pico_status_led) + +# create map/bin/hex file etc. +pico_add_extra_outputs(hello_sleep) + + +add_executable(hello_sleep_usb + hello_sleep.c + ) + +target_link_libraries(hello_sleep_usb pico_stdlib pico_low_power pico_status_led) + +pico_enable_stdio_usb(hello_sleep_usb 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(hello_sleep_usb) + + +add_executable(hello_sleep_sram1 + hello_sleep.c + ) + +target_link_libraries(hello_sleep_sram1 pico_stdlib pico_low_power pico_status_led) + +if (NOT PICO_RP2040) + pico_set_persistent_data_loc(hello_sleep_sram1 0x20040000) +endif() + +# create map/bin/hex file etc. +pico_add_extra_outputs(hello_sleep_sram1) + +if (NOT PICO_RP2040) + if (NOT PICO_RISCV) + add_executable(hello_sleep_encrypted + hello_sleep.c + ) + + target_link_libraries(hello_sleep_encrypted pico_stdlib pico_low_power pico_status_led) + + pico_set_binary_type(hello_sleep_encrypted no_flash) + pico_package_uf2_output(hello_sleep_encrypted) + pico_sign_binary(hello_sleep_encrypted ${CMAKE_SOURCE_DIR}/tools/example_keys/private.pem) + pico_encrypt_binary(hello_sleep_encrypted ${CMAKE_SOURCE_DIR}/tools/example_keys/privateaes.bin ${CMAKE_SOURCE_DIR}/tools/example_keys/ivsalt.bin EMBED NO_CLEAR) + + # create map/bin/hex file etc. + pico_add_extra_outputs(hello_sleep_encrypted) + endif() + + + add_executable(hello_sleep_xip_sram + hello_sleep.c + ) + + target_link_libraries(hello_sleep_xip_sram pico_stdlib pico_low_power pico_status_led) + + pico_set_persistent_data_loc(hello_sleep_xip_sram 0x13ffc000) + + # create map/bin/hex file etc. + pico_add_extra_outputs(hello_sleep_xip_sram) + + + add_executable(hello_sleep_xip_sram_no_flash + hello_sleep.c + ) + + target_link_libraries(hello_sleep_xip_sram_no_flash pico_stdlib pico_low_power pico_status_led) + + pico_set_persistent_data_loc(hello_sleep_xip_sram_no_flash 0x13ffc000) + + pico_set_binary_type(hello_sleep_xip_sram_no_flash no_flash) + pico_package_uf2_output(hello_sleep_xip_sram_no_flash) + + # create map/bin/hex file etc. + pico_add_extra_outputs(hello_sleep_xip_sram_no_flash) +endif() + +if (PICO_RP2040) + add_executable(rtc_clksrc + rtc_clksrc.c + ) + + target_link_libraries(rtc_clksrc pico_stdlib pico_status_led) + + # create map/bin/hex file etc. + pico_add_extra_outputs(rtc_clksrc) +endif() + +add_executable(hello_sleep_gpio + hello_sleep_gpio.c + ) + +target_link_libraries(hello_sleep_gpio pico_stdlib pico_low_power pico_status_led) + +# create map/bin/hex file etc. +pico_add_extra_outputs(hello_sleep_gpio) + + +add_executable(hello_sleep_gpio_usb + hello_sleep_gpio.c + ) + +target_link_libraries(hello_sleep_gpio_usb pico_stdlib pico_low_power pico_status_led) + +pico_enable_stdio_usb(hello_sleep_gpio_usb 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(hello_sleep_gpio_usb) + + +add_custom_target(hello_sleep_examples) +get_directory_property(targets BUILDSYSTEM_TARGETS DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) +foreach(target IN LISTS targets) + get_target_property(type ${target} TYPE) + if(type STREQUAL "EXECUTABLE") + add_dependencies(hello_sleep_examples ${target}) + endif() +endforeach() diff --git a/test/hello_sleep/hello_sleep.c b/test/hello_sleep/hello_sleep.c new file mode 100644 index 000000000..bea59ee02 --- /dev/null +++ b/test/hello_sleep/hello_sleep.c @@ -0,0 +1,315 @@ +/** + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico/stdlib.h" +#include "pico/low_power.h" +#include "pico/aon_timer.h" +#include "pico/status_led.h" +#include "hardware/structs/xip_ctrl.h" + +#define SLEEP_TIME_S 2 +#define SLEEP_TIME_MS SLEEP_TIME_S * 1000 + +#define RTC_GPIO 22 // must support clock input, see the GPIO function table in the datasheet. + +bool repeater(repeating_timer_t *timer) { + if (aon_timer_is_running()) { + printf(" Repeating timer %d at %dms (aon: %dms)", *(uint32_t*)timer->user_data, to_ms_since_boot(get_absolute_time()), to_ms_since_boot(aon_timer_get_absolute_time())); + } else { + printf(" Repeating timer %d at %dms (aon: not running)", *(uint32_t*)timer->user_data, to_ms_since_boot(get_absolute_time())); + } + +#if PICO_NO_FLASH || PICO_COPY_TO_RAM + printf("\n"); +#else + printf(" - Cache hit rate %.2f%%\n", ((float)xip_ctrl_hw->ctr_hit / (float)xip_ctrl_hw->ctr_acc) * 100.0f); +#endif + + status_led_set_state(!status_led_get_state()); + return true; +} + +void chars_available_callback(__unused void *param) { + char buf[16] = {0}; + while (stdio_get_until(buf, sizeof(buf), make_timeout_time_us(10)) > 0) { + printf("Chars available callback: %s\n", buf); + } +} + +#if HAS_POWMAN_TIMER +static bool came_from_pstate = false; +static char powman_last_pwrup[100]; +static char powman_last_pstate[100]; + +int __persistent_data(my_number); + +// Increase this size to see the cache hit rate decrease, when using XIP_SRAM for persistent data +char __persistent_data(large_thing)[0x1000]; + +void pstate_resume_func(pstate_bitset_t *pstate) { + came_from_pstate = true; + memset(powman_last_pwrup, 0, sizeof(powman_last_pwrup)); + memset(powman_last_pstate, 0, sizeof(powman_last_pstate)); + switch (powman_hw->last_swcore_pwrup) { + // 0 = chip reset, for the source of the last reset see + case 1 << 0: strcpy(powman_last_pwrup, "Chip reset"); break; + case 1 << 1: strcpy(powman_last_pwrup, "Pwrup0"); break; + case 1 << 2: strcpy(powman_last_pwrup, "Pwrup1"); break; + case 1 << 3: strcpy(powman_last_pwrup, "Pwrup2"); break; + case 1 << 4: strcpy(powman_last_pwrup, "Pwrup3"); break; + case 1 << 5: strcpy(powman_last_pwrup, "Coresight_pwrup"); break; + case 1 << 6: strcpy(powman_last_pwrup, "Alarm_pwrup"); break; + default: strcpy(powman_last_pwrup, "Unknown pwrup"); break; + } + + if (pstate_bitset_is_set(pstate, POWMAN_POWER_DOMAIN_XIP_CACHE)) strcat(powman_last_pstate, "XIP_CACHE, "); + if (pstate_bitset_is_set(pstate, POWMAN_POWER_DOMAIN_SRAM_BANK0)) strcat(powman_last_pstate, "SRAM_BANK0, "); + if (pstate_bitset_is_set(pstate, POWMAN_POWER_DOMAIN_SRAM_BANK1)) strcat(powman_last_pstate, "SRAM_BANK1, "); + if (pstate_bitset_none_set(pstate)) strcat(powman_last_pstate, "NONE, "); + + pstate_bitset_t default_pstate = pstate_bitset_none(); + low_power_persistent_pstate_get(&default_pstate); + for (int i = 0; i < POWMAN_POWER_DOMAIN_COUNT; i++) { + if (pstate_bitset_is_set(&default_pstate, i) && !pstate_bitset_is_set(pstate, i)) { + strcat(powman_last_pstate, "PERSISTENT_DATA_OFF, "); + if (my_number == 0) my_number = 34567; // initialise my_number to special value + break; + } + } +} +#endif + +int main() { + stdio_init_all(); + status_led_init(); + printf("Hello Sleep!\n"); + + // use a repeating timer on the same TIMER instance; it should be disabled + // during exclusive sleep (todo not sure how it affects power!) + repeating_timer_t repeat; + uint32_t repeater_id = 0; + add_repeating_timer_ms(500, repeater, &repeater_id, &repeat); + + // test stdio_set_chars_available_callback + stdio_set_chars_available_callback(chars_available_callback, NULL); + +#if HAS_POWMAN_TIMER + // use a second repeating timer on the other TIMER instance; it should be gated + // during our sleep (todo not sure how it affects power!) + alarm_pool_t *alarm_pool = alarm_pool_create_on_timer_with_unused_hardware_alarm(timer1_hw, 4); + repeating_timer_t repeat2; + uint32_t repeater2_id = 1; + alarm_pool_add_repeating_timer_ms(alarm_pool, 700, repeater, &repeater2_id, &repeat2); + + if (my_number == 0) { + // initialise persistent data + my_number = 12345; + memset(large_thing, 0x55, sizeof(large_thing)); + // track number of reboots + powman_hw->scratch[3] = 0; + } + + if (came_from_pstate) { + printf("Came from powerup %s with (%s) memory kept on - skipping to end\n", powman_last_pwrup, powman_last_pstate); + if (strstr(powman_last_pstate, "NONE") != NULL) { + goto post_pstate_sram_off; + } else { + goto post_pstate_sram_on; + } + } + + pstate_bitset_t pstate; +#endif + + printf("Waiting %d seconds\n", SLEEP_TIME_S); // so we can see some repeat printfs + busy_wait_ms(SLEEP_TIME_MS); + + absolute_time_t start_time; + static absolute_time_t __persistent_data(wakeup_time); + int64_t diff; + struct timespec ts; + int ret; + + + + // exclusive sleep + printf("Going to sleep for %d seconds via TIMER\n", SLEEP_TIME_S); + + start_time = get_absolute_time(); + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + low_power_sleep_until_timer(timer_hw, wakeup_time, NULL, true); + diff = absolute_time_diff_us(wakeup_time, get_absolute_time()); + printf("Woken up now @%dus since target\n", (int)diff); + if (diff < 0) { + printf("ERROR: Woke up too soon\n"); + return -1; + } + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + + + // non-exclusive sleep + printf("Going to non-exclusive sleep for %d seconds via TIMER\n", SLEEP_TIME_S); + + start_time = get_absolute_time(); + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + low_power_sleep_until_timer(timer_hw, wakeup_time, NULL, false); + diff = absolute_time_diff_us(wakeup_time, get_absolute_time()); + printf("Woken up now @%dus since target\n", (int)diff); + if (diff < 0) { + printf("ERROR: Woke up too soon\n"); + return -1; + } + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + + + // dormant + printf("Going DORMANT for %d seconds via AON TIMER\n", SLEEP_TIME_S); + + // todo, ah; we should start the aon timer; still have to decide what to do about keeping them in sync + start_time = get_absolute_time(); + us_to_timespec(start_time, &ts); + aon_timer_start(&ts); + + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + low_power_dormant_until_aon_timer(wakeup_time, + #if PICO_RP2040 + DORMANT_CLOCK_SOURCE_XOSC, 46875, + #else + DORMANT_CLOCK_SOURCE_LPOSC, XOSC_HZ, + #endif + RTC_GPIO, NULL); + // need to use the AON timer for checking time, since the other timer is unclocked + diff = absolute_time_diff_us(wakeup_time, get_absolute_time()); + if (diff > -1000000 + #ifdef PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS + + (PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS * 1000) + #endif + ) { + printf("ERROR: doesn't seem like timer was stopped\n"); + return - 1; + } + diff = absolute_time_diff_us(wakeup_time, aon_timer_get_absolute_time()); + printf("Woken up now @%dus since target\n", (int)diff); + if (diff < 0) { + printf("WARNING: Woke up too soon - is this within the resolution of the aon timer?\n"); + } + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + + + // powman states +#if HAS_POWMAN_TIMER + // pstate with sram0 on + printf("Going to PSTATE with persistent data on for %d seconds\n", SLEEP_TIME_S); + + if (my_number != 12345) { + printf("ERROR: my_number is %d not 12345 - initialisation issue?\n", my_number); + return -1; + } + my_number = 67890; + + start_time = aon_timer_get_absolute_time(); + + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + ret = low_power_pstate_until_aon_timer(wakeup_time, NULL, pstate_resume_func); + + printf("%d low_power_pstate_until_aon_timer returned\n", ret); + while (true) { + printf("Waiting\n"); + busy_wait_ms(1000); + } + +post_pstate_sram_on: + // track number of reboots + powman_hw->scratch[3]++; + diff = absolute_time_diff_us(wakeup_time, aon_timer_get_absolute_time()); + printf("Woken up now @%dus since target\n", (int)diff); + if (diff < 0) { + printf("WARNING: Woke up too soon - is this within the resolution of the aon timer?\n"); + } else if (diff > 1000000 + #ifdef PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS + + (PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS * 1000) + #endif + ) { + printf("ERROR: Woke up more than %d seconds late\n", (int)(diff / 1000000)); + return -1; + } + + if (my_number != 67890) { + printf("ERROR: my_number is %d not 67890 - SRAM has been re-loaded\n", my_number); + return -1; + } else { + printf("my_number in sram: %d\n", my_number); + } + + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + + + // pstate with sram off + printf("Going to PSTATE with SRAM off for %d seconds\n", SLEEP_TIME_S); + + start_time = aon_timer_get_absolute_time(); + + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + // store in scratch, as not persisting memory over this reboot + powman_hw->scratch[0] = to_us_since_boot(wakeup_time) & 0xFFFFFFFF; + powman_hw->scratch[1] = to_us_since_boot(wakeup_time) >> 32; + pstate = pstate_bitset_none(); + ret = low_power_pstate_until_aon_timer(wakeup_time, &pstate, pstate_resume_func); + + printf("%d low_power_pstate_until_aon_timer returned\n", ret); + while (true) { + printf("Waiting\n"); + busy_wait_ms(1000); + } + +post_pstate_sram_off: + // track number of reboots + powman_hw->scratch[3]++; + // restore from scratch + wakeup_time = from_us_since_boot((uint64_t)powman_hw->scratch[1] << 32 | (uint64_t)powman_hw->scratch[0]); + diff = absolute_time_diff_us(wakeup_time, aon_timer_get_absolute_time()); + printf("Woken up now @%dus since target\n", (int)diff); + if (diff < 0) { + printf("WARNING: Woke up too soon - is this within the resolution of the aon timer?\n"); + } else if (diff > 1000000 + #ifdef PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS + + (PICO_STDIO_USB_CONNECT_WAIT_TIMEOUT_MS * 1000) + #endif + ) { + printf("ERROR: Woke up more than %d seconds late\n", (int)(diff / 1000000)); + return -1; + } + + if (my_number != 34567) { + printf("ERROR: my_number is %d not 34567 - SRAM has not been re-loaded\n", my_number); + return -1; + } else { + printf("my_number in sram: %d\n", my_number); + } + + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + if (powman_hw->scratch[3] != 2) { + printf("ERROR: number of POWMAN reboots was %d not 2\n", powman_hw->scratch[3]); + return -1; + } +#endif + + printf("SUCCESS\n"); + + return 0; +} \ No newline at end of file diff --git a/test/hello_sleep/hello_sleep_gpio.c b/test/hello_sleep/hello_sleep_gpio.c new file mode 100644 index 000000000..00492d284 --- /dev/null +++ b/test/hello_sleep/hello_sleep_gpio.c @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico/stdlib.h" +#include "pico/low_power.h" +#include "pico/aon_timer.h" +#include "pico/status_led.h" + +#define SLEEP_TIME_S 2 +#define SLEEP_TIME_MS SLEEP_TIME_S * 1000 + +bool repeater(repeating_timer_t *timer) { + if (aon_timer_is_running()) { + printf(" Repeating timer at %dms (aon: %dms)\n", to_ms_since_boot(get_absolute_time()), to_ms_since_boot(aon_timer_get_absolute_time())); + } else { + printf(" Repeating timer at %dms (aon: not running)\n", to_ms_since_boot(get_absolute_time())); + } + status_led_set_state(!status_led_get_state()); + return true; +} + +void chars_available_callback(__unused void *param) { + char buf[16] = {0}; + while (stdio_get_until(buf, sizeof(buf), make_timeout_time_us(10)) > 0) { + printf("Chars available callback: %s\n", buf); + } +} + +#if HAS_POWMAN_TIMER +static bool came_from_pstate = false; +static char powman_last_pwrup[100]; +static char powman_last_pstate[100]; + +void pstate_resume_func(pstate_bitset_t *pstate) { + came_from_pstate = true; + memset(powman_last_pwrup, 0, sizeof(powman_last_pwrup)); + memset(powman_last_pstate, 0, sizeof(powman_last_pstate)); + switch (powman_hw->last_swcore_pwrup) { + // 0 = chip reset, for the source of the last reset see + case 1 << 0: strcpy(powman_last_pwrup, "Chip reset"); break; + case 1 << 1: strcpy(powman_last_pwrup, "Pwrup0"); break; + case 1 << 2: strcpy(powman_last_pwrup, "Pwrup1"); break; + case 1 << 3: strcpy(powman_last_pwrup, "Pwrup2"); break; + case 1 << 4: strcpy(powman_last_pwrup, "Pwrup3"); break; + case 1 << 5: strcpy(powman_last_pwrup, "Coresight_pwrup"); break; + case 1 << 6: strcpy(powman_last_pwrup, "Alarm_pwrup"); break; + default: strcpy(powman_last_pwrup, "Unknown pwrup"); break; + } + + if (pstate_bitset_is_set(pstate, POWMAN_POWER_DOMAIN_XIP_CACHE)) strcat(powman_last_pstate, "XIP_CACHE, "); + if (pstate_bitset_is_set(pstate, POWMAN_POWER_DOMAIN_SRAM_BANK0)) strcat(powman_last_pstate, "SRAM_BANK0, "); + if (pstate_bitset_is_set(pstate, POWMAN_POWER_DOMAIN_SRAM_BANK1)) strcat(powman_last_pstate, "SRAM_BANK1, "); + if (pstate_bitset_none_set(pstate)) strcat(powman_last_pstate, "NONE, "); +} +#endif + +int main() { + stdio_init_all(); + status_led_init(); + printf("Hello Sleep!\n"); + // use a repeating timer; it should be gated + // during our sleep (todo not sure how it affects power!) + repeating_timer_t repeat; + add_repeating_timer_ms(500, repeater, NULL, &repeat); + + // test stdio_set_chars_available_callback + stdio_set_chars_available_callback(chars_available_callback, NULL); + +#if HAS_POWMAN_TIMER + if (came_from_pstate) { + printf("Came from powerup %s with (%s) memory kept on - skipping to end\n", powman_last_pwrup, powman_last_pstate); + goto post_pstate_gpio; + } +#endif + + printf("Waiting %d seconds\n", SLEEP_TIME_S); // so we can see some repeat printfs + busy_wait_ms(SLEEP_TIME_MS); + + absolute_time_t start_time; + struct timespec ts; + int ret; + + printf("Going to sleep until GPIO wakeup\n"); + + low_power_sleep_until_pin_state(PICO_DEFAULT_UART_RX_PIN, true, false, NULL, true); + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + printf("Going to non-exclusive sleep until GPIO wakeup\n"); + + // need to keep the timer running + clock_dest_bitset_t keep_enabled = clock_dest_bitset_none(); +#if PICO_RP2040 + clock_dest_bitset_add(&keep_enabled, CLK_DEST_SYS_TIMER); +#else + clock_dest_bitset_add(&keep_enabled, CLK_DEST_SYS_TIMER0); + clock_dest_bitset_add(&keep_enabled, CLK_DEST_REF_TICKS); +#endif + + low_power_sleep_until_pin_state(PICO_DEFAULT_UART_RX_PIN, true, false, &keep_enabled, false); + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + printf("Going to sleep until any wakeup (expecting stdin characters)\n"); + + low_power_sleep_until_irq(NULL); + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + // todo, ah; we should start the aon timer; still have to decide what to do about keeping them in sync + start_time = get_absolute_time(); + us_to_timespec(start_time, &ts); + aon_timer_start(&ts); + + printf("Going DORMANT until GPIO wakeup\n"); + + low_power_dormant_until_pin_state(PICO_DEFAULT_UART_RX_PIN, true, false, DORMANT_CLOCK_SOURCE_ROSC, NULL); + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + +#if HAS_POWMAN_TIMER + printf("Going to PSTATE until GPIO wakeup\n"); + + ret = low_power_pstate_until_pin_state(PICO_DEFAULT_UART_RX_PIN, true, false, NULL, pstate_resume_func); + + printf("%d low_power_pstate_until_pin_state returned\n", ret); + while (true) { + printf("Waiting\n"); + busy_wait_ms(1000); + } + +post_pstate_gpio: + + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); +#endif + + printf("SUCCESS\n"); + + return 0; +} \ No newline at end of file diff --git a/test/hello_sleep/rtc_clksrc.c b/test/hello_sleep/rtc_clksrc.c new file mode 100644 index 000000000..cda603b02 --- /dev/null +++ b/test/hello_sleep/rtc_clksrc.c @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdlib.h" +#include "pico/status_led.h" +#include "pico/sync.h" +#include "hardware/clocks.h" + +#define RTC_GPIO 21 + +bool repeater(repeating_timer_t *timer) { + printf(" Repeating timer at %dms\n", to_ms_since_boot(get_absolute_time())); + status_led_set_state(!status_led_get_state()); + return true; +} + +int main() { + stdio_init_all(); + status_led_init(); + + clock_gpio_init(RTC_GPIO, CLOCKS_CLK_GPOUT3_CTRL_AUXSRC_VALUE_CLK_RTC, 1); + + repeating_timer_t repeat; + add_repeating_timer_ms(500, repeater, NULL, &repeat); + + while (true) __wfi(); + + return 0; +} \ No newline at end of file diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt index ed039d87e..a2a30609b 100644 --- a/test/kitchen_sink/CMakeLists.txt +++ b/test/kitchen_sink/CMakeLists.txt @@ -46,6 +46,7 @@ set(KITCHEN_SINK_LIBS pico_float pico_i2c_slave pico_int64_ops + pico_low_power pico_malloc pico_mem_ops pico_multicore diff --git a/test/kitchen_sink/kitchen_sink.c b/test/kitchen_sink/kitchen_sink.c index 0b712a220..22a453d5a 100644 --- a/test/kitchen_sink/kitchen_sink.c +++ b/test/kitchen_sink/kitchen_sink.c @@ -12,9 +12,13 @@ #include "hardware/dma.h" #include "pico/sync.h" #include "pico/stdlib.h" +#include "pico/util/bitset.h" #if LIB_PICO_BINARY_INFO #include "pico/binary_info.h" #endif +#if LIB_PICO_AON_TIMER +#include "pico/aon_timer.h" +#endif #else #include KITCHEN_SINK_INCLUDE_HEADER #endif @@ -85,6 +89,16 @@ int main(void) { hard_assert(!mutex_try_enter(&mutex, NULL)); hard_assert(recursive_mutex_try_enter(&recursive_mutex, NULL)); hard_assert(recursive_mutex_try_enter(&recursive_mutex, NULL)); + typedef bitset_type_t(47) foop_t; +//#define WOOP encoded_bitset_of3(1, 27, 32) +#define WOOP encoded_bitset_of5(1, 27, 32, 40, 3) + encoded_bitset_foreach(WOOP, printf("Flarn %d\n", bit)); + foop_t fooper; + bitset_init(&fooper, foop_t, 47, 0); + encoded_bitset_foreach(WOOP, bitset_set_bit(&fooper.bitset, bit)); + for(uint i=0;i RAM AT> RAM_STORE + __extra_data_source__ = LOADADDR(.extra_data); +} + +INCLUDE "section_heap.ld" +INCLUDE "section_scratch.ld" +INCLUDE "section_flash_end.ld" +INCLUDE "section_end.ld" +INCLUDE "section_platform_end.ld" \ No newline at end of file diff --git a/test/kitchen_sink/kitchen_sink_simple_overlay_scripts/sections_default.ld b/test/kitchen_sink/kitchen_sink_simple_overlay_scripts/sections_default.ld new file mode 100644 index 000000000..9a69c883a --- /dev/null +++ b/test/kitchen_sink/kitchen_sink_simple_overlay_scripts/sections_default.ld @@ -0,0 +1,24 @@ +INCLUDE "section_default_text.ld" +INCLUDE "section_default_data.ld" + +SECTIONS +{ + PROVIDE(__overlays_start__ = .); + OVERLAY : { + .overlay_first { + KEEP (*(.first*)) + . = ALIGN(4); + } + .overlay_second { + KEEP (*(.second*)) + . = ALIGN(4); + } + } > RAM AT> RAM_STORE + PROVIDE(__overlays_end__ = .); +} + +INCLUDE "section_heap.ld" +INCLUDE "section_scratch.ld" +INCLUDE "section_flash_end.ld" +INCLUDE "section_end.ld" +INCLUDE "section_platform_end.ld" diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 5a6795ef2..6849c1778 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -65,6 +65,12 @@ define_property(TARGET BRIEF_DOCS "OTP page storing the AES key" FULL_DOCS "OTP page storing the AES key" ) +define_property(TARGET + PROPERTY PICOTOOL_ENC_NO_CLEAR + INHERITED + BRIEF_DOCS "Do not clear SRAM when loading encrypted binary" + FULL_DOCS "Do not clear SRAM when loading encrypted binary" +) define_property(TARGET PROPERTY PICOTOOL_ENC_SIGFILE INHERITED @@ -480,7 +486,7 @@ function(pico_embed_pt_in_binary TARGET PTFILE) ) endfunction() -# pico_encrypt_binary(TARGET AESFILE IVFILE [SIGFILE ] [EMBED] [MBEDTLS] [OTP_KEY_PAGE ]) +# pico_encrypt_binary(TARGET AESFILE IVFILE [SIGFILE ] [EMBED] [MBEDTLS] [OTP_KEY_PAGE ] [NO_CLEAR]) # \brief_nodesc\ Encrypt the taget binary # # Encrypt the target binary with the given AES key (should be a binary @@ -510,8 +516,9 @@ endfunction() # \param\ EMBED Embed a decryption stage into the encrypted binary # \param\ MBEDTLS Use MbedTLS based decryption stage (faster, but less secure) # \param\ OTP_KEY_PAGE The OTP page storing the AES key +# \param\ NO_CLEAR Do not clear SRAM when loading encrypted binary function(pico_encrypt_binary TARGET AESFILE IVFILE) - set(options EMBED MBEDTLS) + set(options EMBED MBEDTLS NO_CLEAR) set(oneValueArgs OTP_KEY_PAGE SIGFILE) # set(multiValueArgs ) cmake_parse_arguments(PARSE_ARGV 3 ENC "${options}" "${oneValueArgs}" "${multiValueArgs}") @@ -543,6 +550,12 @@ function(pico_encrypt_binary TARGET AESFILE IVFILE) ) endif() + if (ENC_NO_CLEAR) + set_target_properties(${TARGET} PROPERTIES + PICOTOOL_ENC_NO_CLEAR TRUE + ) + endif() + if (ENC_OTP_KEY_PAGE) set_target_properties(${TARGET} PROPERTIES PICOTOOL_OTP_KEY_PAGE ${ENC_OTP_KEY_PAGE} @@ -709,9 +722,17 @@ function(picotool_postprocess_binary TARGET) ${picotool_args} COMMAND_EXPAND_LISTS VERBATIM) + + # tag that this binary will be sealed + target_compile_definitions(${TARGET} PRIVATE PICO_PICOTOOL_SEALED=1) endif() # Encryption if (picotool_aesfile AND picotool_ivfile) + get_target_property(enc_no_clear ${TARGET} PICOTOOL_ENC_NO_CLEAR) + if (NOT enc_no_clear) + list(APPEND picotool_encrypt_args "--clear") + endif() + get_target_property(picotool_embed_decryption ${TARGET} PICOTOOL_EMBED_DECRYPTION) if (picotool_embed_decryption) list(APPEND picotool_encrypt_args "--embed") diff --git a/tools/bazel_build.py b/tools/bazel_build.py index 3e2f64b5d..eb9752ac5 100755 --- a/tools/bazel_build.py +++ b/tools/bazel_build.py @@ -52,6 +52,9 @@ "//test/pico_sha256_test:pico_sha256_test", "//test/pico_stdio_test:pico_stdio_test", "//test/pico_time_test:pico_time_test", + "//test/hello_sleep:hello_sleep", + "//test/hello_sleep:hello_sleep_gpio", + "//test/hello_sleep:rtc_clksrc", "//test/pico_async_context_test:pico_async_context_test", # Pretty much only Picotool and pioasm build on Windows. @@ -92,6 +95,7 @@ "//test/pico_float_test:hazard3_test_gen", # RP2040 only "//test/kitchen_sink:kitchen_sink_blocked_ram", + "//test/hello_sleep:rtc_clksrc", # TODO: RISC-V support. "//test/pico_float_test:pico_float_test_hazard3", ) @@ -134,6 +138,7 @@ "//test/pico_float_test:hazard3_test_gen", # RP2040 only "//test/kitchen_sink:kitchen_sink_blocked_ram", + "//test/hello_sleep:rtc_clksrc", # TODO: RISC-V support. "//test/pico_float_test:pico_float_test_hazard3", # not supported by clang @@ -172,6 +177,7 @@ "//test/pico_float_test:hazard3_test_gen", # RP2040 only "//test/kitchen_sink:kitchen_sink_blocked_ram", + "//test/hello_sleep:rtc_clksrc", # TODO: RISC-V support. "//test/pico_float_test:pico_float_test_hazard3", ) @@ -214,6 +220,8 @@ def build_all_configurations(picotool_dir): build_targets = [ t for t in default_build_targets if t not in config["exclusions"] ] + print("Build targets: ", build_targets) + print("Exclusions: ", config["exclusions"]) build_targets.extend(config["extra_targets"]) args = list(config["args"]) diff --git a/tools/example_keys/ivsalt.bin b/tools/example_keys/ivsalt.bin new file mode 100644 index 000000000..fb9ef50b8 --- /dev/null +++ b/tools/example_keys/ivsalt.bin @@ -0,0 +1 @@ +Œ¶ÆxìÛ%Ü^ùÂ=T’ÄŒ \ No newline at end of file diff --git a/tools/extract_cmake_functions.py b/tools/extract_cmake_functions.py index 48020768e..27ca51a6b 100755 --- a/tools/extract_cmake_functions.py +++ b/tools/extract_cmake_functions.py @@ -73,6 +73,7 @@ def __init__(self, message, errors): 'pico_standard_link': ('Pico Standard Link', 'CMake functions to configure the linker'), 'pico_stdio': ('Pico Standard I/O', 'CMake functions to configure the standard I/O library'), 'pico_pio': ('Pico PIO', 'CMake functions to generate PIO headers'), + 'pico_low_power': ('Pico Low Power', 'CMake functions to configure the low power library'), 'other': ('Other', 'Other CMake functions'), }