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_base_headers/include/pico/types.h b/src/common/pico_base_headers/include/pico/types.h index 010181eef..68f7a804e 100644 --- a/src/common/pico_base_headers/include/pico/types.h +++ b/src/common/pico_base_headers/include/pico/types.h @@ -50,7 +50,7 @@ typedef uint64_t absolute_time_t; * \ingroup timestamp */ static inline uint64_t to_us_since_boot(absolute_time_t t) { -#ifdef PICO_DEBUG_ABSOLUTE_TIME_T +#if PICO_OPAQUE_ABSOLUTE_TIME_T return t._private_us_since_boot; #else return t; @@ -65,7 +65,7 @@ static inline uint64_t to_us_since_boot(absolute_time_t t) { * \ingroup timestamp */ static inline void update_us_since_boot(absolute_time_t *t, uint64_t us_since_boot) { -#ifdef PICO_DEBUG_ABSOLUTE_TIME_T +#if PICO_OPAQUE_ABSOLUTE_TIME_T assert(us_since_boot <= INT64_MAX); t->_private_us_since_boot = us_since_boot; #else @@ -85,7 +85,7 @@ static inline absolute_time_t from_us_since_boot(uint64_t us_since_boot) { return t; } -#ifdef NDEBUG +#if !PICO_OPAQUE_ABSOLUTE_TIME_T #define ABSOLUTE_TIME_INITIALIZED_VAR(name, value) name = value #else #define ABSOLUTE_TIME_INITIALIZED_VAR(name, value) name = {value} diff --git a/src/common/pico_time/include/pico/time.h b/src/common/pico_time/include/pico/time.h index 43d5ed7a9..d05ce83f6 100644 --- a/src/common/pico_time/include/pico/time.h +++ b/src/common/pico_time/include/pico/time.h @@ -89,6 +89,18 @@ static inline uint32_t to_ms_since_boot(absolute_time_t t) { return us_to_ms(us); } +/*! fn to_ms_64_since_boot + * \ingroup timestamp + * \brief Convert a timestamp into a number of 64-bit milliseconds since boot. + * \param t an absolute_time_t value to convert + * \return the number of milliseconds since boot represented by t + * \sa to_us_since_boot() + */ + static inline uint64_t to_ms_64_since_boot(absolute_time_t t) { + uint64_t us = to_us_since_boot(t); + return us / 1000ull; +} + /*! \brief Return a timestamp value obtained by adding a number of microseconds to another timestamp * \ingroup timestamp * diff --git a/src/common/pico_util/BUILD.bazel b/src/common/pico_util/BUILD.bazel index d1e73c0cb..8095e7560 100644 --- a/src/common/pico_util/BUILD.bazel +++ b/src/common/pico_util/BUILD.bazel @@ -6,11 +6,13 @@ package(default_visibility = ["//visibility:public"]) cc_library( name = "pico_util", srcs = [ + "fixed_bitset.c", "datetime.c", "pheap.c", "queue.c", ], hdrs = [ + "include/pico/util/fixed_bitset.h", "include/pico/util/datetime.h", "include/pico/util/pheap.h", "include/pico/util/queue.h", diff --git a/src/common/pico_util/CMakeLists.txt b/src/common/pico_util/CMakeLists.txt index 86921dd4e..cff7766a0 100644 --- a/src/common/pico_util/CMakeLists.txt +++ b/src/common/pico_util/CMakeLists.txt @@ -8,6 +8,7 @@ if (NOT TARGET pico_util) pico_add_impl_library(pico_util) target_sources(pico_util INTERFACE ${CMAKE_CURRENT_LIST_DIR}/datetime.c + ${CMAKE_CURRENT_LIST_DIR}/fixed_bitset.c ${CMAKE_CURRENT_LIST_DIR}/pheap.c ${CMAKE_CURRENT_LIST_DIR}/queue.c ) diff --git a/src/common/pico_util/fixed_bitset.c b/src/common/pico_util/fixed_bitset.c new file mode 100644 index 000000000..39f15bc35 --- /dev/null +++ b/src/common/pico_util/fixed_bitset.c @@ -0,0 +1,26 @@ +/* +* Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "include/pico/util/fixed_bitset.h" + +fixed_bitset_t *fixed_bitset_flip_all(fixed_bitset_t *bitset) { + check_fixed_bitset(bitset); + for (uint i=0;iword_size;i++) { + bitset->words[i] = ~bitset->words[i]; + } + return bitset; +} + +bool fixed_bitset_is_empty(fixed_bitset_t *bitset) { + check_fixed_bitset(bitset); + int i=0; + for (i=0;iword_size-1;i++) { + if (bitset->words[i]) return false; + } + // we don't guarantee that bits above the size aren't set, so mask them off + return !(bitset->words[i] << (32u -(bitset->size & 31u))); +} + diff --git a/src/common/pico_util/include/pico/util/fixed_bitset.h b/src/common/pico_util/include/pico/util/fixed_bitset.h new file mode 100644 index 000000000..7eea75734 --- /dev/null +++ b/src/common/pico_util/include/pico/util/fixed_bitset.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_UTIL_FIXED_BITSET_H +#define _PICO_UTIL_FIXED_BITSET_H + +#include "pico.h" + +/** \file fixed_bitset.h + * \defgroup fixed_bitset fixed_bitset + * \brief Simple fixed-size bitset implementation + * + * \ingroup pico_util + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t size; \ + uint16_t word_size; \ + uint32_t words[]; +} fixed_bitset_t; + +/*! \brief Macro used to define a fixed-size bitset of a given size + * \ingroup fixed_bitset + * This macro is used to declare the type of a fixed-size bitset. It is used as follows: + * ``` + * typedef fixed_bitset_type(17) my_bitset_t; + * ``` + * will define a new bitset type called `my_bitset_t` that can hold 17 boolean values. + * + * The type can be used as `my_bitset_t bitset;` to declare a new bitset. + * + * \param N the number of boolean values in the bitset + */ +#define fixed_bitset_type(N) union { \ + fixed_bitset_t bitset; \ + struct { \ + uint16_t size; \ + uint16_t word_size; \ + uint32_t words[((N) + 31) / 32]; \ + } sized_bitset; \ +} +#define fixed_bitset_sizeof_for(N) ((((N) + 63u) / 32u) * 4u) + +/*! \brief Macro used to create a bitset with all bits set to a value + * \ingroup fixed_bitset + * \param type the type of the bitset + * \param N the number of bits in the bitset + * \param fill the value to set the bits to (0 or 1) + * \return the bitset + */ +#define fixed_bitset_with_fill(type, N, fill) ({ type bitset; fixed_bitset_init(&bitset, type, N, fill); bitset; }) + +// Quick test that the bitset macros give the correct size +extern fixed_bitset_type(32) __not_real_bitset32; +extern fixed_bitset_type(33) __not_real_bitset33; +static_assert(sizeof(__not_real_bitset32) == fixed_bitset_sizeof_for(1),""); +static_assert(sizeof(__not_real_bitset33) == fixed_bitset_sizeof_for(37), ""); +static_assert(sizeof(__not_real_bitset33) != fixed_bitset_sizeof_for(1), ""); + +/*! \brief Initialize a bitset + * \ingroup fixed_bitset + * \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 fixed_bitset_init(ptr, type, N, fill) ({ \ + assert(sizeof(type) == fixed_bitset_sizeof_for(N)); \ + __unused type *type_check = ptr; \ + (ptr)->bitset.size = N; \ + (ptr)->bitset.word_size = ((N) + 31u) / 32u; \ + __builtin_memset(&(ptr)->bitset.words, (fill) ? 0xff : 0, (ptr)->bitset.word_size * sizeof(uint32_t)); \ +}) + +/*! \brief Get the size of the bitset + * \ingroup fixed_bitset + * \param bitset the bitset to get the size of + * \return the size of the bitset + */ +static inline uint fixed_bitset_size(const fixed_bitset_t *bitset) { + return bitset->size; +} + +/*! \brief Get the size of the bitset in words + * \ingroup fixed_bitset + * \param bitset the bitset to get the size of + * \return the size of the bitset in words + */ +static inline uint fixed_bitset_word_size(const fixed_bitset_t *bitset) { + return bitset->word_size; +} + +/*! \brief Check that the bitset is valid + * \ingroup fixed_bitset + * This function will assert if the bitset is not valid. + * \param bitset the bitset to check + */ +static inline void check_fixed_bitset(__unused const fixed_bitset_t *bitset) { + assert(bitset->word_size == (bitset->size + 31) / 32); +} + +/*! \brief Write a word in the bitset + * \ingroup fixed_bitset + * \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 fixed_bitset_t *fixed_bitset_write_word(fixed_bitset_t *bitset, uint word_num, uint32_t value) { + check_fixed_bitset(bitset); + if (word_num < fixed_bitset_word_size(bitset)) { + bitset->words[word_num] = value; + } + return bitset; +} + +/*! \brief Read a word in the bitset + * \ingroup fixed_bitset + * \param bitset the bitset + * \param word_num the word number to read from + * \return the value of the word + */ +static inline uint32_t fixed_bitset_read_word(const fixed_bitset_t *bitset, uint word_num) { + check_fixed_bitset(bitset); + if (word_num < fixed_bitset_word_size(bitset)) { + return bitset->words[word_num]; + } + return 0; +} + +/*! \brief Clear all bits in the bitset + * \ingroup fixed_bitset + * \param bitset the bitset + * \return the bitset + */ +static inline fixed_bitset_t *fixed_bitset_clear_all(fixed_bitset_t *bitset) { + check_fixed_bitset(bitset); + __builtin_memset(bitset->words, 0, bitset->word_size * sizeof(uint32_t)); + return bitset; +} + +/*! \brief Set all bits in the bitset + * \ingroup fixed_bitset + * \param bitset the bitset + * \return the bitset + */ +static inline fixed_bitset_t *fixed_bitset_set_all(fixed_bitset_t *bitset) { + check_fixed_bitset(bitset); + __builtin_memset(bitset->words, 0xff, bitset->word_size * sizeof(uint32_t)); + return bitset; +} + +/*! \brief Flip all bits in the bitset + * \ingroup fixed_bitset + * \param bitset the bitset + * \return the bitset + */ +fixed_bitset_t *fixed_bitset_flip_all(fixed_bitset_t *bitset); + +/*! \brief Determine if bitset is empty + * \ingroup fixed_bitset + * \param bitset the bitset + * \return true if not bits are set + */ +bool fixed_bitset_is_empty(fixed_bitset_t *bitset); + +/*! \brief Set a single bit in the bitset + * \ingroup fixed_bitset + * \param bitset the bitset + * \param bit_index the bit to set + * \return the bitset + */ +static inline fixed_bitset_t *fixed_bitset_set(fixed_bitset_t *bitset, uint bit_index) { + check_fixed_bitset(bitset); + if (bit_index < bitset->size) { + bitset->words[bit_index / 32u] |= 1u << (bit_index % 32u); + } + return bitset; +} + +/*! \brief Clear a single bit in the bitset + * \ingroup fixed_bitset + * \param bitset the bitset + * \param bit_index the bit to clear + * \return the bitset + */ +static inline fixed_bitset_t *fixed_bitset_clear(fixed_bitset_t *bitset, uint bit_index) { + check_fixed_bitset(bitset); + if (bit_index < bitset->size) { + bitset->words[bit_index / 32u] &= ~(1u << (bit_index % 32u)); + } + return bitset; +} + +/*! \brief Flip a single bit in the bitset + * \ingroup fixed_bitset + * \param bitset the bitset + * \param bit_index the bit to flip + * \return the bitset + */ +static inline fixed_bitset_t *fixed_bitset_flip(fixed_bitset_t *bitset, uint bit_index) { + check_fixed_bitset(bitset); + if (bit_index < bitset->size) { + bitset->words[bit_index / 32u] ^= 1u << (bit_index % 32u); + } + return bitset; +} + + +/*! \brief Get the value of a single bit in the bitset + * \ingroup fixed_bitset + * \param bitset the bitset + * \param bit_index the bit to get the value of + * \return the value of the bit + */ +static inline bool fixed_bitset_get(const fixed_bitset_t *bitset, uint bit_index) { + check_fixed_bitset(bitset); + assert(bit_index < bitset->size); +// if (bit < bitset->size) { + return bitset->words[bit_index / 32u] & (1u << (bit_index % 32u)); +// } + return false; +} + +/*! \brief Check if two bitsets are equal + * \ingroup fixed_bitset + * \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 fixed_bitset_equal(const fixed_bitset_t *bitset1, const fixed_bitset_t *bitset2) { + check_fixed_bitset(bitset1); + check_fixed_bitset(bitset2); + assert(bitset1->size == bitset2->size); + return __builtin_memcmp(bitset1->words, bitset2->words, bitset1->word_size * sizeof(uint32_t)) == 0; +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/rp2040/pico_platform/memmap_blocked_ram.ld b/src/rp2040/pico_platform/memmap_blocked_ram.ld index 7dfa94c33..345036962 100644 --- a/src/rp2040/pico_platform/memmap_blocked_ram.ld +++ b/src/rp2040/pico_platform/memmap_blocked_ram.ld @@ -33,7 +33,8 @@ INCLUDE "memmap_default.incl" * │ ├── section_ram_vector_table.incl rp2_common/pico_standard_link * │ ├── section_uninitialized_data.incl rp2_common/pico_standard_link * │ ├── section_default_data.incl rp2_common/pico_standard_link - * │ └── section_bss.incl rp2_common/pico_standard_link + * │ ├── section_bss.incl rp2_common/pico_standard_link + * │ └── section_persistent_data.incl rp2_common/pico_standard_link * ├── section_generated_post_data.incl rp2_common/pico_standard_link * ├── section_extra_post_data.incl rp2_common/pico_standard_link * ├── section_heap.incl rp2_common/pico_standard_link diff --git a/src/rp2040/pico_platform/memmap_copy_to_ram.ld b/src/rp2040/pico_platform/memmap_copy_to_ram.ld index 411b62d48..a9e541e8d 100644 --- a/src/rp2040/pico_platform/memmap_copy_to_ram.ld +++ b/src/rp2040/pico_platform/memmap_copy_to_ram.ld @@ -29,7 +29,8 @@ INCLUDE "memmap_copy_to_ram.incl" * ├── section_extra_post_text.incl rp2_common/pico_standard_link * ├── sections_copy_to_ram_data.incl rp2_common/pico_standard_link * │ ├── section_copy_to_ram_data.incl rp2_common/pico_standard_link - * │ └── section_bss.incl rp2_common/pico_standard_link + * │ ├── section_bss.incl rp2_common/pico_standard_link + * │ └── section_persistent_data.incl rp2_common/pico_standard_link * ├── section_generated_post_data.incl rp2_common/pico_standard_link * ├── section_extra_post_data.incl rp2_common/pico_standard_link * ├── section_heap.incl rp2_common/pico_standard_link diff --git a/src/rp2040/pico_platform/memmap_default.ld b/src/rp2040/pico_platform/memmap_default.ld index 3b6e09409..f7e561e7c 100644 --- a/src/rp2040/pico_platform/memmap_default.ld +++ b/src/rp2040/pico_platform/memmap_default.ld @@ -30,7 +30,8 @@ INCLUDE "memmap_default.incl" * │ ├── section_ram_vector_table.incl rp2_common/pico_standard_link * │ ├── section_uninitialized_data.incl rp2_common/pico_standard_link * │ ├── section_default_data.incl rp2_common/pico_standard_link - * │ └── section_bss.incl rp2_common/pico_standard_link + * │ ├── section_bss.incl rp2_common/pico_standard_link + * │ └── section_persistent_data.incl rp2_common/pico_standard_link * ├── section_generated_post_data.incl rp2_common/pico_standard_link * ├── section_extra_post_data.incl rp2_common/pico_standard_link * ├── section_heap.incl rp2_common/pico_standard_link diff --git a/src/rp2040/pico_platform/memmap_no_flash.ld b/src/rp2040/pico_platform/memmap_no_flash.ld index 00a434989..9e86b518d 100644 --- a/src/rp2040/pico_platform/memmap_no_flash.ld +++ b/src/rp2040/pico_platform/memmap_no_flash.ld @@ -24,7 +24,8 @@ INCLUDE "memmap_no_flash.incl" * ├── sections_no_flash_data.incl rp2_common/pico_standard_link * │ ├── section_no_flash_data.incl rp2_common/pico_standard_link * │ ├── section_uninitialized_data.incl rp2_common/pico_standard_link - * │ └── section_bss.incl rp2_common/pico_standard_link + * │ ├── section_bss.incl rp2_common/pico_standard_link + * │ └── section_persistent_data.incl rp2_common/pico_standard_link * ├── section_generated_post_data.incl rp2_common/pico_standard_link * ├── section_extra_post_data.incl rp2_common/pico_standard_link * ├── section_heap.incl rp2_common/pico_standard_link diff --git a/src/rp2350/pico_platform/memmap_copy_to_ram.ld b/src/rp2350/pico_platform/memmap_copy_to_ram.ld index 2a534b940..d8c1d8bec 100644 --- a/src/rp2350/pico_platform/memmap_copy_to_ram.ld +++ b/src/rp2350/pico_platform/memmap_copy_to_ram.ld @@ -29,7 +29,8 @@ INCLUDE "memmap_copy_to_ram.incl" * ├── section_extra_post_text.incl rp2_common/pico_standard_link * ├── sections_copy_to_ram_data.incl rp2_common/pico_standard_link * │ ├── section_copy_to_ram_data.incl rp2_common/pico_standard_link - * │ └── section_bss.incl rp2_common/pico_standard_link + * │ ├── section_bss.incl rp2_common/pico_standard_link + * │ └── section_persistent_data.incl rp2_common/pico_standard_link * ├── section_generated_post_data.incl rp2_common/pico_standard_link * ├── section_extra_post_data.incl rp2_common/pico_standard_link * ├── section_heap.incl rp2_common/pico_standard_link diff --git a/src/rp2350/pico_platform/memmap_default.ld b/src/rp2350/pico_platform/memmap_default.ld index 965541bd5..b1d5121df 100644 --- a/src/rp2350/pico_platform/memmap_default.ld +++ b/src/rp2350/pico_platform/memmap_default.ld @@ -30,7 +30,8 @@ INCLUDE "memmap_default.incl" * │ ├── section_ram_vector_table.incl rp2_common/pico_standard_link * │ ├── section_uninitialized_data.incl rp2_common/pico_standard_link * │ ├── section_default_data.incl rp2_common/pico_standard_link - * │ └── section_bss.incl rp2_common/pico_standard_link + * │ ├── section_bss.incl rp2_common/pico_standard_link + * │ └── section_persistent_data.incl rp2_common/pico_standard_link * ├── section_generated_post_data.incl rp2_common/pico_standard_link * ├── section_extra_post_data.incl rp2_common/pico_standard_link * ├── section_heap.incl rp2_common/pico_standard_link diff --git a/src/rp2350/pico_platform/memmap_no_flash.ld b/src/rp2350/pico_platform/memmap_no_flash.ld index 67acac37c..34b3a33e7 100644 --- a/src/rp2350/pico_platform/memmap_no_flash.ld +++ b/src/rp2350/pico_platform/memmap_no_flash.ld @@ -24,7 +24,8 @@ INCLUDE "memmap_no_flash.incl" * ├── sections_no_flash_data.incl rp2_common/pico_standard_link * │ ├── section_no_flash_data.incl rp2_common/pico_standard_link * │ ├── section_uninitialized_data.incl rp2_common/pico_standard_link - * │ └── section_bss.incl rp2_common/pico_standard_link + * │ ├── section_bss.incl rp2_common/pico_standard_link + * │ └── section_persistent_data.incl rp2_common/pico_standard_link * ├── section_generated_post_data.incl rp2_common/pico_standard_link * ├── section_extra_post_data.incl rp2_common/pico_standard_link * ├── section_heap.incl rp2_common/pico_standard_link diff --git a/src/rp2_common/BUILD.bazel b/src/rp2_common/BUILD.bazel index fe1c0c3e3..92a23da25 100644 --- a/src/rp2_common/BUILD.bazel +++ b/src/rp2_common/BUILD.bazel @@ -71,8 +71,10 @@ alias( "//src/rp2_common/hardware_base:__pkg__", "//src/rp2_common/hardware_irq:__pkg__", "//src/rp2_common/hardware_pll:__pkg__", + "//src/rp2_common/hardware_rtc:__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/CMakeLists.txt b/src/rp2_common/hardware_clocks/CMakeLists.txt index 95b16c2f3..575081b8c 100644 --- a/src/rp2_common/hardware_clocks/CMakeLists.txt +++ b/src/rp2_common/hardware_clocks/CMakeLists.txt @@ -10,6 +10,7 @@ pico_mirrored_target_link_libraries(hardware_clocks INTERFACE hardware_vreg hardware_watchdog hardware_xosc + pico_util ) if (PICO_USE_FASTEST_SUPPORTED_CLOCK) diff --git a/src/rp2_common/hardware_clocks/clocks.c b/src/rp2_common/hardware_clocks/clocks.c index 2bcaa5741..3bc61b692 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 < fixed_bitset_word_size(&dests->bitset); i++) { + fixed_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 < fixed_bitset_word_size(&dests->bitset); i++) { + clocks_hw->sleep_en[i] = fixed_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 863df976c..8a7ed6ef1 100644 --- a/src/rp2_common/hardware_clocks/include/hardware/clocks.h +++ b/src/rp2_common/hardware_clocks/include/hardware/clocks.h @@ -9,6 +9,7 @@ #include "pico.h" #include "hardware/structs/clocks.h" +#include "pico/util/fixed_bitset.h" #ifdef __cplusplus extern "C" { @@ -590,6 +591,33 @@ static inline bool set_sys_clock_khz(uint32_t freq_khz, bool required) { return false; } +typedef fixed_bitset_type(NUM_CLOCK_DESTINATIONS) clock_dest_bitset_t; +#define clock_dest_bitset_none() fixed_bitset_with_fill(clock_dest_bitset_t, NUM_CLOCK_DESTINATIONS, 0) +#define clock_dest_bitset_all() fixed_bitset_with_fill(clock_dest_bitset_t, NUM_CLOCK_DESTINATIONS, 1) + +static inline clock_dest_bitset_t *clock_dest_bitset_clear(clock_dest_bitset_t *dests) { + fixed_bitset_clear_all(&dests->bitset); + return dests; +} + +static inline clock_dest_bitset_t *clock_dest_bitset_add_all(clock_dest_bitset_t *dests) { + fixed_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) { + fixed_bitset_set(&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) { + fixed_bitset_clear(&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 b4053b382..9a021fe27 100644 --- a/src/rp2_common/hardware_irq/include/hardware/irq.h +++ b/src/rp2_common/hardware_irq/include/hardware/irq.h @@ -282,6 +282,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..48fc471d4 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/fixed_bitset.h" #ifdef __cplusplus extern "C" { @@ -26,14 +27,21 @@ extern "C" { #define PARAM_ASSERTIONS_ENABLED_HARDWARE_POWMAN 0 #endif +// PICO_CONFIG: PICO_POWMAN_CALIBRATE_LPOSC_FROM_OTP, Use the OTP calibration value for the low power oscillator frequency, type=bool, default=1, group=hardware_powman +#ifndef PICO_POWMAN_CALIBRATE_LPOSC_FROM_OTP +#define PICO_POWMAN_CALIBRATE_LPOSC_FROM_OTP 1 +#endif + /*! \brief Use the ~32KHz low power oscillator as the powman timer source * \ingroup hardware_powman + * \note The frequency is set to the value stored in the OTP, or the reset value if + * there is no OTP value set or PICO_POWMAN_CALIBRATE_LPOSC_FROM_OTP is 0 */ void powman_timer_set_1khz_tick_source_lposc(void); /*! \brief Use the low power oscillator (specifying frequency) as the powman timer source * \ingroup hardware_powman - * \param lposc_freq_hz specify an exact lposc freq to trim it + * \param lposc_freq_hz specify an exact lposc freq to trim it, or 0 to use the reset values */ void powman_timer_set_1khz_tick_source_lposc_with_hz(uint32_t lposc_freq_hz); @@ -135,11 +143,22 @@ static inline bool powman_timer_is_running(void) { /*! \brief Stop the powman timer * \ingroup hardware_powman + * \note On the next start, the timer will resume from the last set time, + * so if you want to pause the timer and resume from the current time, you + * should use \ref powman_timer_pause */ static inline void powman_timer_stop(void) { powman_clear_bits(&powman_hw->timer, POWMAN_TIMER_RUN_BITS); } +/*! \brief Pause the powman timer + * \ingroup hardware_powman + */ +static inline void powman_timer_pause(void) { + powman_clear_bits(&powman_hw->timer, POWMAN_TIMER_RUN_BITS); + powman_timer_set_ms(powman_timer_get_ms()); +} + /*! \brief Start the powman timer * \ingroup hardware_powman */ @@ -167,9 +186,52 @@ 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 fixed_bitset_type(POWMAN_POWER_DOMAIN_COUNT) pstate_bitset_t; +#define pstate_bitset_none() fixed_bitset_with_fill(pstate_bitset_t, POWMAN_POWER_DOMAIN_COUNT, 0) +#define pstate_bitset_all() fixed_bitset_with_fill(pstate_bitset_t, POWMAN_POWER_DOMAIN_COUNT, 1) + +static inline pstate_bitset_t *pstate_bitset_remove_all(pstate_bitset_t *domains) { + fixed_bitset_clear_all(&domains->bitset); + return domains; +} + +static inline pstate_bitset_t *pstate_bitset_add_all(pstate_bitset_t *domains) { + fixed_bitset_set_all(&domains->bitset); + return domains; +} + +static inline pstate_bitset_t *pstate_bitset_add(pstate_bitset_t *domains, powman_power_domain_t domain) { + fixed_bitset_set(&domains->bitset, domain); + return domains; +} + +static inline pstate_bitset_t *pstate_bitset_remove(pstate_bitset_t *domains, powman_power_domain_t domain) { + fixed_bitset_clear(&domains->bitset, domain); + return domains; +} + +static inline bool pstate_bitset_is_set(pstate_bitset_t *domains, powman_power_domain_t domain) { + return fixed_bitset_get(&domains->bitset, domain); +} + +static inline bool pstate_bitset_none_set(pstate_bitset_t *domains) { + return fixed_bitset_is_empty(&domains->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)); + fixed_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 fixed_bitset_read_word(&domains->bitset, 0); +} + /*! \brief Get the current power state * \ingroup hardware_powman */ @@ -180,8 +242,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_powman/powman.c b/src/rp2_common/hardware_powman/powman.c index 4af59f502..893e29146 100644 --- a/src/rp2_common/hardware_powman/powman.c +++ b/src/rp2_common/hardware_powman/powman.c @@ -12,6 +12,9 @@ #include "hardware/gpio.h" #include "hardware/powman.h" +#if PICO_POWMAN_CALIBRATE_LPOSC_FROM_OTP +#include "hardware/regs/otp_data.h" +#endif #ifndef PICO_POWMAN_DEBUG #define PICO_POWMAN_DEBUG 0 @@ -62,20 +65,38 @@ uint64_t powman_timer_get_ms(void) { } void powman_timer_set_1khz_tick_source_lposc(void) { - powman_timer_set_1khz_tick_source_lposc_with_hz(32768); +#if PICO_POWMAN_CALIBRATE_LPOSC_FROM_OTP + uint16_t* lposc_calib_data = (uint16_t*)OTP_DATA_BASE + OTP_DATA_LPOSC_CALIB_ROW; + if (*lposc_calib_data == 0) { + powman_timer_set_1khz_tick_source_lposc_with_hz(0); + } else { + powman_timer_set_1khz_tick_source_lposc_with_hz(*lposc_calib_data); + } +#else + powman_timer_set_1khz_tick_source_lposc_with_hz(0); +#endif } void powman_timer_set_1khz_tick_source_lposc_with_hz(uint32_t lposc_freq_hz) { bool was_running = powman_timer_is_running(); - if (was_running) powman_timer_stop(); - uint32_t lposc_freq_khz = lposc_freq_hz / 1000; - uint32_t lposc_freq_khz_frac16 = (lposc_freq_hz % 1000) * 65536 / 1000; - powman_write(&powman_hw->lposc_freq_khz_int, lposc_freq_khz); - powman_write(&powman_hw->lposc_freq_khz_frac, lposc_freq_khz_frac16); - powman_set_bits(&powman_hw->timer, POWMAN_TIMER_USE_LPOSC_BITS); - if (was_running) { - powman_timer_start(); - while(!(powman_hw->timer & POWMAN_TIMER_USING_LPOSC_BITS)); + uint32_t lposc_freq_khz = lposc_freq_hz == 0 ? POWMAN_LPOSC_FREQ_KHZ_INT_RESET : lposc_freq_hz / 1000; + uint32_t lposc_freq_khz_frac16 = lposc_freq_hz == 0 ? POWMAN_LPOSC_FREQ_KHZ_FRAC_RESET : (lposc_freq_hz % 1000) * 65536 / 1000; + if (lposc_freq_khz != powman_hw->lposc_freq_khz_int || lposc_freq_khz_frac16 != powman_hw->lposc_freq_khz_frac) { + // Frequency change needed, so need to stop the timer, set the frequency, and start the timer with USE_LPOSC + if (was_running) powman_timer_pause(); + powman_write(&powman_hw->lposc_freq_khz_int, lposc_freq_khz); + powman_write(&powman_hw->lposc_freq_khz_frac, lposc_freq_khz_frac16); + powman_set_bits(&powman_hw->timer, POWMAN_TIMER_USE_LPOSC_BITS); + if (was_running) { + powman_timer_start(); + while(!(powman_hw->timer & POWMAN_TIMER_USING_LPOSC_BITS)); + } + } else { + // No frequency change needed, so can just set USE_LPOSC while running + powman_set_bits(&powman_hw->timer, POWMAN_TIMER_USE_LPOSC_BITS); + if (was_running) { + while(!(powman_hw->timer & POWMAN_TIMER_USING_LPOSC_BITS)); + } } } @@ -85,15 +106,24 @@ void powman_timer_set_1khz_tick_source_xosc(void) { void powman_timer_set_1khz_tick_source_xosc_with_hz(uint32_t xosc_freq_hz) { bool was_running = powman_timer_is_running(); - if (was_running) powman_timer_stop(); uint32_t xosc_freq_khz = xosc_freq_hz / 1000; uint32_t xosc_freq_khz_frac16 = (xosc_freq_hz % 1000) * 65536 / 1000; - powman_write(&powman_hw->xosc_freq_khz_int, xosc_freq_khz); - powman_write(&powman_hw->xosc_freq_khz_frac, xosc_freq_khz_frac16); - powman_set_bits(&powman_hw->timer, POWMAN_TIMER_USE_XOSC_BITS); - if (was_running) { - powman_timer_start(); - while(!(powman_hw->timer & POWMAN_TIMER_USING_XOSC_BITS)); + if (xosc_freq_khz != powman_hw->xosc_freq_khz_int || xosc_freq_khz_frac16 != powman_hw->xosc_freq_khz_frac) { + // Frequency change needed, so need to stop the timer, set the frequency, and start the timer with USE_XOSC + if (was_running) powman_timer_pause(); + powman_write(&powman_hw->xosc_freq_khz_int, xosc_freq_khz); + powman_write(&powman_hw->xosc_freq_khz_frac, xosc_freq_khz_frac16); + powman_set_bits(&powman_hw->timer, POWMAN_TIMER_USE_XOSC_BITS); + if (was_running) { + powman_timer_start(); + while(!(powman_hw->timer & POWMAN_TIMER_USING_XOSC_BITS)); + } + } else { + // No frequency change needed, so can just set USE_XOSC while running + powman_set_bits(&powman_hw->timer, POWMAN_TIMER_USE_XOSC_BITS); + if (was_running) { + while(!(powman_hw->timer & POWMAN_TIMER_USING_XOSC_BITS)); + } } } @@ -112,7 +142,7 @@ static inline uint32_t gpio_to_powman_ext_time_ref_source(uint gpio, uint32_t de static void powman_timer_use_gpio(uint32_t gpio, uint32_t use, uint32_t using) { bool was_running = powman_timer_is_running(); - if (was_running) powman_timer_stop(); + if (was_running) powman_timer_pause(); uint32_t source = gpio_to_powman_ext_time_ref_source(gpio, 0); gpio_set_input_enabled(gpio, true); powman_write(&powman_hw->ext_time_ref, source); @@ -152,7 +182,7 @@ powman_power_state powman_get_power_state(void) { int powman_set_power_state(powman_power_state state) { // Clear req ignored in case it has been set powman_clear_bits(&powman_hw->state, POWMAN_STATE_REQ_IGNORED_BITS); - powman_debug("powman: Requesting state %x\n", state); + powman_debug("powman: Requesting state %"PRIx32"\n", state); powman_write(&powman_hw->state, (~state << POWMAN_STATE_REQ_LSB) & POWMAN_STATE_REQ_BITS); // Has it been ignored? @@ -176,7 +206,7 @@ int powman_set_power_state(powman_power_state state) { // Note if the powerdown is being blocked by a pending pwrup request we will break out of this and return a failure // Clk pow is slow so can take a few clk_pow cycles for waiting to turn up - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 500; i++) { if (powman_hw->state & POWMAN_STATE_WAITING_BITS) { return PICO_OK; } @@ -189,7 +219,7 @@ int powman_set_power_state(powman_power_state state) { // Wait while the state is changing then return true as we will be in the new state powman_debug("powman: waiting for state change\n"); while(powman_hw->state & POWMAN_STATE_CHANGING_BITS) tight_loop_contents(); - powman_debug("powman: state changed to %x\n", state); + powman_debug("powman: state changed to %"PRIx32"\n", state); return PICO_OK; } 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..53f2bee53 --- /dev/null +++ b/src/rp2_common/hardware_rosc/include/hardware/rosc.h @@ -0,0 +1,91 @@ +/* + * 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 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 Measure the frequency of the Ring Oscillator + * \ingroup hardware_rosc + * + * This will only be accurate if clk_ref is currently running from an accurate source (eg the XOSC). + * + * \return The frequency of the Ring Oscillator in kHz. + */ +uint rosc_measure_freq_khz(void); + +// ROSC BADWRITE is not reliable, see RP2040-E10 +// 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 + * + * This would normally clear the bad write flag and asserts that the write is okay. + * However, ROSC BADWRITE is not reliable (see RP2040-E10) so we don't do this. + * + * \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..e1eafdfc9 --- /dev/null +++ b/src/rp2_common/hardware_rosc/rosc.c @@ -0,0 +1,40 @@ +/* + * 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" + + +uint rosc_measure_freq_khz(void) { + return frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC); +} + +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/BUILD.bazel b/src/rp2_common/hardware_rtc/BUILD.bazel index f239f8d8f..a13d1e4b9 100644 --- a/src/rp2_common/hardware_rtc/BUILD.bazel +++ b/src/rp2_common/hardware_rtc/BUILD.bazel @@ -11,7 +11,7 @@ cc_library( target_compatible_with = ["//bazel/constraint:rp2040"], deps = [ "//src/rp2_common:hardware_structs", - "//src/rp2_common:pico_platform", + "//src/rp2_common:pico_platform_internal", "//src/rp2_common/hardware_clocks", "//src/rp2_common/hardware_irq", "//src/rp2_common/hardware_resets", diff --git a/src/rp2_common/hardware_rtc/include/hardware/rtc.h b/src/rp2_common/hardware_rtc/include/hardware/rtc.h index 9e05c7afb..7457f72d4 100644 --- a/src/rp2_common/hardware_rtc/include/hardware/rtc.h +++ b/src/rp2_common/hardware_rtc/include/hardware/rtc.h @@ -10,6 +10,12 @@ #include "pico.h" #if HAS_RP2040_RTC #include "hardware/structs/rtc.h" + +// The RTC clock frequency is 48MHz divided by power of 2 (to ensure an integer +// division ratio will be used in the clocks block). A divisor of 1024 generates +// an RTC clock tick of 46875Hz. This frequency is relatively close to the +// customary 32 or 32.768kHz 'slow clock' crystals and provides good timing resolution. +#define RTC_CLOCK_FREQ_HZ (USB_CLK_HZ / 1024) #endif /** \file hardware/rtc.h @@ -90,6 +96,17 @@ void rtc_enable_alarm(void); */ void rtc_disable_alarm(void); +/*! \brief Run the RTC from an external clock source through GPIO + * \ingroup hardware_sleep + * + * \note This function will return false if the external clock source is not running. + * + * \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..6eb53a49d 100644 --- a/src/rp2_common/hardware_rtc/rtc.c +++ b/src/rp2_common/hardware_rtc/rtc.c @@ -188,3 +188,33 @@ void rtc_disable_alarm(void) { tight_loop_contents(); } } + +bool rtc_run_from_external_source(uint32_t src_hz, uint gpio_pin) { + bool success = clock_configure_gpin(clk_rtc, gpio_pin, src_hz, RTC_CLOCK_FREQ_HZ); + if (success) { + // Ensure external source is actually running + uint32_t rtc_freq = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC); + if (rtc_freq < ((RTC_CLOCK_FREQ_HZ / KHZ) - 5) || rtc_freq > ((RTC_CLOCK_FREQ_HZ / KHZ) + 5)) { + // Frequency is not within 1kHz of the expected frequency + success = false; + // reconfigure the clock to the default configuration + #if (USB_CLK_HZ % RTC_CLOCK_FREQ_HZ == 0) + // this doesn't pull in 64 bit arithmetic + clock_configure_int_divider(clk_rtc, + 0, // No GLMUX + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, + USB_CLK_HZ, + USB_CLK_HZ / RTC_CLOCK_FREQ_HZ); + + #else + clock_configure(clk_rtc, + 0, // No GLMUX + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB, + USB_CLK_HZ, + RTC_CLOCK_FREQ_HZ); + + #endif + } + } + return success; +} \ 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..ef7d32c64 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 @@ -9,6 +9,7 @@ #include "pico.h" #include +#include "pico/time.h" #include "pico/util/datetime.h" #include "hardware/regs/intctrl.h" @@ -180,6 +181,23 @@ 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 Convenience method to get the timestamp a number of milliseconds from the current time of the AON timer + * \ingroup pico_aon_timer + * + * \param ms the number of milliseconds to add to the current timestamp + * \return the future timestamp + */ + static inline absolute_time_t aon_timer_make_timeout_time_ms(uint32_t ms) { + return delayed_by_ms(aon_timer_get_absolute_time(), ms); +} + /** * \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..dac8473ee --- /dev/null +++ b/src/rp2_common/pico_low_power/CMakeLists.txt @@ -0,0 +1,60 @@ +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 + ) + +# To work around lack of support for ${CMAKE_CURRENT_FUNCTION_LIST_DIR} in CMake <3.17 +set(PICO_LOW_POWER_CURRENT_PATH ${CMAKE_CURRENT_LIST_DIR}) +pico_register_common_scope_var(PICO_LOW_POWER_CURRENT_PATH) +pico_promote_common_scope_vars() + +# 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. +# +# \param\ PERSISTENT_DATA_LOC The persistent data location to set +function(pico_set_persistent_data_loc TARGET PERSISTENT_DATA_LOC) + if (PICO_RP2040) + message(FATAL_ERROR "pico_set_persistent_data_loc is not supported on RP2040") + endif() + + # Configure override section_persistent_data.incl for the target + configure_file(${PICO_LOW_POWER_CURRENT_PATH}/section_persistent_data.incl.template ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}/section_persistent_data.incl @ONLY) + pico_add_linker_script_override_path(${TARGET} ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}) + + if (PERSISTENT_DATA_LOC LESS 0x20000000) + # XIP_SRAM, so 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_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..0fd3ead78 --- /dev/null +++ b/src/rp2_common/pico_low_power/include/pico/low_power.h @@ -0,0 +1,383 @@ +/* + * 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" +#include "pico/time.h" +#include "pico/aon_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 + +// PICO_CONFIG: PICO_LOW_POWER_MIN_AON_SLEEP_TIME_MS, Minimum supported time to spend in sleep using the AON timer in milliseconds, type=int, default=2000 on RP2040, 10 otherwise, group=pico_low_power +#ifndef PICO_LOW_POWER_MIN_AON_SLEEP_TIME_MS +#if PICO_RP2040 +#define PICO_LOW_POWER_MIN_AON_SLEEP_TIME_MS 2000 +#else +#define PICO_LOW_POWER_MIN_AON_SLEEP_TIME_MS 10 +#endif +#endif + +// PICO_CONFIG: PICO_LOW_POWER_MIN_DORMANT_TIME_MS, Minimum supported time to spend dormant in milliseconds, type=int, default=2000 on RP2040, 10 otherwise, group=pico_low_power +#ifndef PICO_LOW_POWER_MIN_DORMANT_TIME_MS +#if PICO_RP2040 +#define PICO_LOW_POWER_MIN_DORMANT_TIME_MS 2000 +#else +#define PICO_LOW_POWER_MIN_DORMANT_TIME_MS 10 +#endif +#endif + +// PICO_CONFIG: PICO_LOW_POWER_MIN_PSTATE_TIME_MS, Minimum supported time to spend in Pstate in milliseconds, type=int, default=10, group=pico_low_power +#ifndef PICO_LOW_POWER_MIN_PSTATE_TIME_MS +#define PICO_LOW_POWER_MIN_PSTATE_TIME_MS 10 +#endif + +#include "hardware/clocks.h" +#if HAS_RP2040_RTC +#include "hardware/rtc.h" +#endif +#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 PICO_RP2040 +#define DORMANT_CLOCK_SOURCE_DEFAULT DORMANT_CLOCK_SOURCE_XOSC +#else +#define DORMANT_CLOCK_SOURCE_DEFAULT DORMANT_CLOCK_SOURCE_LPOSC +#endif + +#if PICO_RP2040 +#define DORMANT_CLOCK_HZ_DEFAULT RTC_CLOCK_FREQ_HZ +#else +#define DORMANT_CLOCK_HZ_DEFAULT 0 // ignored +#endif + +#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 time using AON timer + * \ingroup pico_low_power + * Sleep until the AON timer reaches the specified value. The clocks specified in keep_enabled will be kept enabled during sleep, along with clocks required + * for the AON timer. If exclusive is true, only the AON timer interrupt will be listened for, otherwise other interrupts will also be listened for. + * + * \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 AON timer interrupt, or other interrupts. + * \return 0 on success, non-zero on error. + */ +int low_power_sleep_until_aon_timer(absolute_time_t until, const clock_dest_bitset_t *keep_enabled, bool exclusive); + + +/*! \brief Sleep until GPIO 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_gpio_pin_state(uint gpio_pin, bool edge, bool high, const clock_dest_bitset_t *keep_enabled, bool exclusive); + +/*! \brief Set the external clock source for the AON timer + * \ingroup pico_low_power + * Set the external clock source for the AON timer. This is only used on RP2040. + * + * \param src_hz The frequency of the external clock source. + * \param gpio_pin The GPIO pin to use for the external clock source. + * \return 0 on success, non-zero on error. + */ +#if PICO_RP2040 +int low_power_set_external_clock_source(uint src_hz, uint gpio_pin); +#else +static inline int low_power_set_external_clock_source(__unused uint src_hz, __unused uint gpio_pin) { + return PICO_OK; +} +#endif + +/*! \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 + * This requires an external clock source to be set using \ref low_power_set_external_clock_source before calling this function. + * If the external clock source is not set, or it is not running, this will return PICO_ERROR_PRECONDITION_NOT_MET. + * \endif + * + * \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 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, const clock_dest_bitset_t *keep_enabled); + +/*! \brief Go dormant until GPIO 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. For the lowest power consumption, you should use + * DORMANT_CLOCK_SOURCE_ROSC instead, as the GPIO interrupt does not require a clock. + * \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_gpio_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 GPIO 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_gpio_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 + + +// Convenience functions to avoid needing the correct absolute_time_t + +/*! \brief Start the AON timer at a specific time in milliseconds + * \ingroup pico_low_power + * See \ref aon_timer_start for more information. + * + * If the AON timer is already running, this function will restart it + * from the specified time. + * + * \param ms The time in milliseconds to start the AON timer at. + * \return true on success, false on failure. + */ +static inline bool low_power_start_aon_timer_at_time_ms(uint64_t ms) { + struct timespec ts; + ms_to_timespec(ms, &ts); + return aon_timer_start(&ts); +} + +/*! \brief Start the AON timer at the current system time + * \ingroup pico_low_power + * + * See \ref aon_timer_start for more information. + * + * If the AON timer is already running, this function will not restart it. + * + * \return true on success, false on failure. + */ +static inline bool low_power_start_aon_timer(void) { + if (aon_timer_is_running()) return true; + return low_power_start_aon_timer_at_time_ms(0); +} + +/*! \brief Sleep for a number of microseconds + * \ingroup pico_low_power + * See \ref low_power_sleep_until_default_timer for more information. + * + * \param us The number of microseconds to sleep. + * \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_for_us(timer_hw_t *timer, uint64_t us, const clock_dest_bitset_t *keep_enabled, bool exclusive) { + return low_power_sleep_until_timer(timer, make_timeout_time_us(us), keep_enabled, exclusive); +} + +/*! \brief Sleep for a number of milliseconds + * \ingroup pico_low_power + * See \ref low_power_sleep_until_default_timer for more information. + * + * \param ms The number of milliseconds to sleep. + * \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_for_ms(uint32_t ms, const clock_dest_bitset_t *keep_enabled, bool exclusive) { + return low_power_sleep_until_default_timer(make_timeout_time_ms(ms), keep_enabled, exclusive); +} + +/*! \brief Go dormant for a number of milliseconds + + * \ingroup pico_low_power + * See \ref low_power_dormant_until_aon_timer for more information. + * + * \param ms The number of milliseconds to go dormant for. + * \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. + */ +static inline int low_power_dormant_for_ms(uint32_t ms, dormant_clock_source_t dormant_clock_source, const clock_dest_bitset_t *keep_enabled) { + low_power_start_aon_timer(); + return low_power_dormant_until_aon_timer(aon_timer_make_timeout_time_ms(ms), dormant_clock_source, keep_enabled); +} + +#if HAS_POWMAN_TIMER +/*! \brief Go to Pstate for a number of milliseconds + * \ingroup pico_low_power + * See \ref low_power_pstate_until_aon_timer for more information. + * + * \param ms The number of milliseconds to go to Pstate for. + * \param pstate The Pstate to use. + * \param resume_func The function to call on reboot. + * \return 0 on success, non-zero on error. + */ +static inline int low_power_pstate_for_ms(uint32_t ms, pstate_bitset_t *pstate, low_power_pstate_resume_func resume_func) { + low_power_start_aon_timer(); + return low_power_pstate_until_aon_timer(aon_timer_make_timeout_time_ms(ms), pstate, resume_func); +} +#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..d622846d3 --- /dev/null +++ b/src/rp2_common/pico_low_power/low_power.c @@ -0,0 +1,810 @@ +/* + * 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 bool is_timestamp_from_aon_timer(absolute_time_t timestamp) { + return to_us_since_boot(timestamp) % 1000 == 0; +} + +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(); + // Ensure debugger does not prevent power down + powman_set_debug_power_request_ignored(true); + // Switch powman timer to lposc explicitly, which will also use the calibrated frequency + powman_timer_set_1khz_tick_source_lposc(); +} + +static void post_pstate_change(void) { +} +#endif + +static 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 +} + +static 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); + } +} + +#if PICO_RP2040 +static uint dormant_rtc_src_hz = 0; +static uint dormant_rtc_gpio_pin; +int low_power_set_external_clock_source(uint src_hz, uint gpio_pin) { + dormant_rtc_src_hz = src_hz; + dormant_rtc_gpio_pin = gpio_pin; + return PICO_OK; +} +#endif // inline in header for other platforms + +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_aon_timer(absolute_time_t until, + const clock_dest_bitset_t *keep_enabled, bool exclusive) { + if (!aon_timer_is_running()) { + return PICO_ERROR_PRECONDITION_NOT_MET; + } + + if (!is_timestamp_from_aon_timer(until)) { + return PICO_ERROR_INVALID_DATA; + } + + if (to_ms_64_since_boot(aon_timer_get_absolute_time()) + PICO_LOW_POWER_MIN_AON_SLEEP_TIME_MS > to_ms_64_since_boot(until)) { + // Prevent race condition where the timer fires before we can go to sleep + // by setting a minimum time for sleep + return PICO_ERROR_INVALID_ARG; + } + + 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_RTC_RTC); +#elif PICO_RP2350 + clock_dest_bitset_add(&local_keep_enabled, CLK_DEST_REF_POWMAN); +#else + #error Unknown processor +#endif + + add_library_clocks(&local_keep_enabled); + + struct timespec ts; + us_to_timespec(to_us_since_boot(until), &ts); + event_happened = false; + aon_timer_enable_alarm(&ts, low_power_wakeup, false); + + if (exclusive) save_and_disable_other_interrupts(aon_timer_get_irq_num()); + + 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(); + + aon_timer_disable_alarm(); + + post_clock_gating(); + + if (exclusive) restore_other_interrupts(); + + return 0; +} + +int low_power_sleep_until_gpio_pin_state(uint gpio_pin, bool edge, bool high, + const clock_dest_bitset_t *keep_enabled, bool exclusive) { + + invalid_params_if_and_return(PICO_LOW_POWER, gpio_pin >= NUM_BANK0_GPIOS, PICO_ERROR_INVALID_ARG); + 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 + + 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 +static 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 + // RTC should already be configured to run from the external source +#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 +static 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(); +} + +static void low_power_go_dormant(dormant_clock_source_t dormant_clock_source) { + invalid_params_if(PICO_LOW_POWER, + 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, + const clock_dest_bitset_t *keep_enabled) { + if (!aon_timer_is_running()) { + return PICO_ERROR_PRECONDITION_NOT_MET; + } + + if (!is_timestamp_from_aon_timer(until)) { + return PICO_ERROR_INVALID_DATA; + } + + if (to_ms_64_since_boot(aon_timer_get_absolute_time()) + PICO_LOW_POWER_MIN_DORMANT_TIME_MS > to_ms_64_since_boot(until)) { + // Prevent race condition where the timer fires before we can go dormant + // by setting a minimum time for dormant + return PICO_ERROR_INVALID_ARG; + } + + clock_dest_bitset_t local_keep_enabled; + replace_null_enable_values(keep_enabled, &local_keep_enabled); + +#if PICO_RP2040 + if (dormant_rtc_src_hz == 0) { + return PICO_ERROR_PRECONDITION_NOT_MET; + } + // The RTC must be run from an external source, since the dormant source will be inactive + if (!rtc_run_from_external_source(dormant_rtc_src_hz, dormant_rtc_gpio_pin)) { + return PICO_ERROR_PRECONDITION_NOT_MET; + } + clock_dest_bitset_add(&local_keep_enabled, CLK_DEST_RTC_RTC); +#elif PICO_RP2350 + 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 + + low_power_setup_clocks_for_dormant(dormant_clock_source); + + struct timespec ts; + us_to_timespec(to_us_since_boot(until), &ts); + event_happened = false; + aon_timer_enable_alarm(&ts, NULL, 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); + + low_power_wake_from_dormant(); + +#if PICO_RP2350 + if (dormant_clock_source == DORMANT_CLOCK_SOURCE_LPOSC) + powman_timer_set_1khz_tick_source_xosc(); +#endif + + return 0; +} + +int low_power_dormant_until_gpio_pin_state(uint gpio_pin, bool edge, bool high, + dormant_clock_source_t dormant_clock_source, + const clock_dest_bitset_t *keep_enabled) { + + invalid_params_if_and_return(PICO_LOW_POWER, gpio_pin >= NUM_BANK0_GPIOS, PICO_ERROR_INVALID_ARG); + + 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 + + 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__[]; + +static 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_remove_all(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; +} + +static 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) { + if (!aon_timer_is_running()) { + return PICO_ERROR_PRECONDITION_NOT_MET; + } + + if (!is_timestamp_from_aon_timer(until)) { + return PICO_ERROR_INVALID_DATA; + } + + if (to_ms_64_since_boot(aon_timer_get_absolute_time()) + PICO_LOW_POWER_MIN_PSTATE_TIME_MS > to_ms_64_since_boot(until)) { + // Prevent race condition where the timer fires before we can go to pstate + // by setting a minimum time for pstate + return PICO_ERROR_INVALID_ARG; + } + powman_enable_alarm_wakeup_at_ms(to_ms_64_since_boot(until)); + + return low_power_go_pstate(pstate, resume_func); +} + +int low_power_pstate_until_gpio_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; + } + // Switch powman timer back to xosc + powman_timer_set_1khz_tick_source_xosc(); + } +} +#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_low_power/section_persistent_data.incl.template b/src/rp2_common/pico_low_power/section_persistent_data.incl.template new file mode 100644 index 000000000..6f5916939 --- /dev/null +++ b/src/rp2_common/pico_low_power/section_persistent_data.incl.template @@ -0,0 +1,9 @@ +SECTIONS +{ + .persistent_data @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_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/BUILD.bazel b/src/rp2_common/pico_runtime_init/BUILD.bazel index 0af3b23fe..d6108fb1b 100644 --- a/src/rp2_common/pico_runtime_init/BUILD.bazel +++ b/src/rp2_common/pico_runtime_init/BUILD.bazel @@ -18,6 +18,15 @@ cc_library( ], ) +alias( + name = "pico_runtime_init_extra_link", + actual = select({ + "//bazel/constraint:rp2040": "//src/rp2_common/hardware_rtc", + "//conditions:default": "//bazel:empty_cc_lib", + }), + visibility = ["//visibility:private"], +) + cc_library( name = "pico_runtime_init_link", srcs = [ @@ -39,6 +48,7 @@ cc_library( "//src/rp2_common/hardware_vreg", "//src/rp2_common/pico_bootrom", "//src/rp2_common/pico_runtime", + "pico_runtime_init_extra_link", ], alwayslink = True, ) diff --git a/src/rp2_common/pico_runtime_init/CMakeLists.txt b/src/rp2_common/pico_runtime_init/CMakeLists.txt index 60d1833e0..f8f1daa6a 100644 --- a/src/rp2_common/pico_runtime_init/CMakeLists.txt +++ b/src/rp2_common/pico_runtime_init/CMakeLists.txt @@ -21,6 +21,9 @@ endif() if (TARGET hardware_vreg) pico_mirrored_target_link_libraries(pico_runtime_init INTERFACE hardware_vreg) endif() +if (TARGET hardware_rtc) + pico_mirrored_target_link_libraries(pico_runtime_init INTERFACE hardware_rtc) +endif() # pico/runtime_init.h includes pico/runtime.h target_link_libraries(pico_runtime_init_headers INTERFACE pico_runtime_headers) \ No newline at end of file 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_runtime_init/runtime_init_clocks.c b/src/rp2_common/pico_runtime_init/runtime_init_clocks.c index a86fe8052..b812843cf 100644 --- a/src/rp2_common/pico_runtime_init/runtime_init_clocks.c +++ b/src/rp2_common/pico_runtime_init/runtime_init_clocks.c @@ -13,16 +13,8 @@ #include "hardware/timer.h" #include "hardware/vreg.h" #include "hardware/xosc.h" -#if PICO_RP2040 -#include "hardware/regs/rtc.h" -#endif - -#if PICO_RP2040 -// The RTC clock frequency is 48MHz divided by power of 2 (to ensure an integer -// division ratio will be used in the clocks block). A divisor of 1024 generates -// an RTC clock tick of 46875Hz. This frequency is relatively close to the -// customary 32 or 32.768kHz 'slow clock' crystals and provides good timing resolution. -#define RTC_CLOCK_FREQ_HZ (USB_CLK_HZ / 1024) +#if HAS_RP2040_RTC +#include "hardware/rtc.h" #endif static void start_all_ticks(void) { diff --git a/src/rp2_common/pico_standard_link/script_include/section_heap.incl b/src/rp2_common/pico_standard_link/script_include/section_heap.incl index 240e118d4..1f7983432 100644 --- a/src/rp2_common/pico_standard_link/script_include/section_heap.incl +++ b/src/rp2_common/pico_standard_link/script_include/section_heap.incl @@ -4,6 +4,11 @@ SECTIONS { + .dummy_pre_heap (NOLOAD): + { + /* Dummy section to make sure we're back in normal RAM for the heap when HEAP_LOC isn't defined */ + . = .; + } > RAM .heap DEFINED(HEAP_LOC) ? HEAP_LOC : . (NOLOAD): { __end__ = .; 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..fa41535ed --- /dev/null +++ b/src/rp2_common/pico_standard_link/script_include/section_persistent_data.incl @@ -0,0 +1,13 @@ +/* Defines the following symbols for use by code: + __persistent_data_start__, __persistent_data_end__ +*/ + +SECTIONS +{ + .persistent_data (NOLOAD) : { + __persistent_data_start__ = .; + *(.persistent_data*) + . = ALIGN(4); + } > RAM + 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 be242e5e1..a52f7027b 100644 --- a/src/rp2_common/pico_stdio_usb/stdio_usb.c +++ b/src/rp2_common/pico_stdio_usb/stdio_usb.c @@ -262,8 +262,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); @@ -272,6 +270,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 74321bc78..d3996654d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,9 +1,12 @@ add_subdirectory(pico_test) +set_directory_properties(PROPERTIES PICO_ALLOW_EXAMPLE_KEYS 1) + add_subdirectory(pico_stdlib_test) add_subdirectory(pico_stdio_test) add_subdirectory(pico_time_test) add_subdirectory(pico_divider_test) +add_subdirectory(fixed_bitset_test) if (PICO_ON_DEVICE) add_subdirectory(panic_function_test) add_subdirectory(pico_float_test) @@ -14,6 +17,7 @@ if (PICO_ON_DEVICE) add_subdirectory(cmsis_test) add_subdirectory(pico_sem_test) add_subdirectory(pico_sha256_test) + add_subdirectory(pico_low_power_test) add_subdirectory(pico_async_context_test) endif() @@ -33,7 +37,7 @@ if (PICO_TEST_FILE_GENERATOR AND EXISTS ${PICO_TEST_FILE_GENERATOR}) # - PICO_TEST_FAILURE_STRING: Failure string for the test (default is "FAILURE_STRING") # Can be used to force failure even when the test prints the success string # - PICO_TEST_BUDDY_FILE: File to run on buddy device for the test (default is NONE) - # For example, low power dormant tests on RP2040 require rtc_clksrc running on the buddy device + # For example, low power tests use external_sleep_timer running on the buddy device # This can either be a path relative to the test target, or an absolute path # - PICO_TEST_SKIP_IN_CI: Whether to skip the test in ci (default is FALSE) # If TRUE, the test is not included in the ci test suite diff --git a/test/fixed_bitset_test/BUILD.bazel b/test/fixed_bitset_test/BUILD.bazel new file mode 100644 index 000000000..9e96248db --- /dev/null +++ b/test/fixed_bitset_test/BUILD.bazel @@ -0,0 +1,10 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("//bazel:defs.bzl", "compatible_with_rp2") + +# TODO: Add these tests to the Bazel build. +filegroup( + name = "unsupported_tests", + srcs = [ + "fixed_bitset_test.c", + ], +) diff --git a/test/fixed_bitset_test/CMakeLists.txt b/test/fixed_bitset_test/CMakeLists.txt new file mode 100644 index 000000000..b39b6868a --- /dev/null +++ b/test/fixed_bitset_test/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(fixed_bitset_test fixed_bitset_test.c) + +target_link_libraries(fixed_bitset_test PRIVATE pico_stdlib pico_util) +pico_add_extra_outputs(fixed_bitset_test) diff --git a/test/fixed_bitset_test/fixed_bitset_test.c b/test/fixed_bitset_test/fixed_bitset_test.c new file mode 100644 index 000000000..28f3ff1ff --- /dev/null +++ b/test/fixed_bitset_test/fixed_bitset_test.c @@ -0,0 +1,56 @@ +/* +* Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico/stdlib.h" +#include "pico/util/fixed_bitset.h" + +#define CHECK(b, message, ...) ({ if (!(b)) { printf("FAILED: " message "\n", ##__VA_ARGS__); exit(1); } }) + +typedef fixed_bitset_type(4) bitset4_t; +typedef fixed_bitset_type(47) bitset47_t; +typedef fixed_bitset_type(77) bitset77_t; + +int main() { + stdio_init_all(); + +#define FIXED_BITSET_TESTS(name, type, n)\ + type name;\ + fixed_bitset_init(&name, type, n, 0);\ + for (int i=0;i=0;i--) {\ + CHECK(fixed_bitset_get(&name.bitset, i), "Bit %d should be set", i);\ + fixed_bitset_clear(&name.bitset, i);\ + CHECK(!fixed_bitset_get(&name.bitset, i), "Bit %d should be clear", i);\ + CHECK(!i == fixed_bitset_is_empty(&name.bitset), "Bitset should be empty once last bit is cleared");\ + }\ + \ + fixed_bitset_init(&name, type, n, 1);\ + for (int i=0;i=0;i--) {\ + CHECK(fixed_bitset_get(&name.bitset, i), "Bit %d should be set", i);\ + fixed_bitset_clear(&name.bitset, i);\ + CHECK(!fixed_bitset_get(&name.bitset, i), "Bit %d should be clear", i);\ + CHECK(!i == fixed_bitset_is_empty(&name.bitset), "Bitset should be empty once last bit is cleared");\ + }\ + + FIXED_BITSET_TESTS(b4, bitset4_t, 4); + FIXED_BITSET_TESTS(b47, bitset47_t, 47); + FIXED_BITSET_TESTS(b77, bitset77_t, 77); + + puts("PASSED"); +} \ No newline at end of file diff --git a/test/kitchen_sink/CMakeLists.txt b/test/kitchen_sink/CMakeLists.txt index d594b9caf..19bc4d930 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 @@ -194,6 +195,11 @@ pico_add_extra_outputs(kitchen_sink_printf_none) pico_set_printf_implementation(kitchen_sink_printf_none none) set_target_properties(kitchen_sink_printf_none PROPERTIES PICO_TEST_SUCCESS_STRING "printf support is disabled") +add_executable(kitchen_sink_opaque_absolute_time ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c) +target_link_libraries(kitchen_sink_opaque_absolute_time kitchen_sink_libs kitchen_sink_options) +pico_add_extra_outputs(kitchen_sink_opaque_absolute_time) +target_compile_definitions(kitchen_sink_opaque_absolute_time PRIVATE KITCHEN_SINK_ID="opaque absolute_time binary" PICO_OPAQUE_ABSOLUTE_TIME_T=1) + if (NOT KITCHEN_SINK_NO_BINARY_TYPE_VARIANTS) add_executable(kitchen_sink_copy_to_ram ${CMAKE_CURRENT_LIST_DIR}/kitchen_sink.c) pico_set_binary_type(kitchen_sink_copy_to_ram copy_to_ram) diff --git a/test/kitchen_sink/kitchen_sink.c b/test/kitchen_sink/kitchen_sink.c index 81b9fee30..5a82a1672 100644 --- a/test/kitchen_sink/kitchen_sink.c +++ b/test/kitchen_sink/kitchen_sink.c @@ -14,9 +14,13 @@ #include "hardware/exception.h" #include "pico/sync.h" #include "pico/stdlib.h" +#include "pico/util/fixed_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 @@ -68,6 +72,14 @@ void svc_call(void) { exit(0); } +#if LIB_PICO_AON_TIMER +static bool aon_timer_done = false; +void spoop(void) { + printf("XXXX YARGLE XXXX\n"); + aon_timer_done = true; +} +#endif + int main(void) { spiggle(); @@ -131,6 +143,18 @@ int main(void) { printf("extra_data after load = %d\n", extra_data); #endif #endif +#if LIB_PICO_AON_TIMER + aon_timer_start_with_timeofday(); + struct timespec ts; + ts.tv_sec = 2; + ts.tv_nsec = 1000000000 / 2; + aon_timer_enable_alarm(&ts, spoop, false); + while (!aon_timer_done) { + aon_timer_get_time(&ts); + printf("%ld %ld\n", (long)ts.tv_sec, ts.tv_nsec); + busy_wait_ms(500); + } +#endif #ifndef __riscv exception_set_exclusive_handler(SVCALL_EXCEPTION, svc_call); // this should compile as we are Cortex-M diff --git a/test/kitchen_sink/kitchen_sink_ram_section_scripts/sections_default.ld b/test/kitchen_sink/kitchen_sink_ram_section_scripts/sections_default.ld new file mode 100644 index 000000000..9ffef1a28 --- /dev/null +++ b/test/kitchen_sink/kitchen_sink_ram_section_scripts/sections_default.ld @@ -0,0 +1,19 @@ +INCLUDE "section_default_text.ld" +INCLUDE "section_default_data.ld" + +SECTIONS +{ + .extra_data : { + __extra_data_start__ = .; + *(.extra_data*) + . = ALIGN(4); + __extra_data_end__ = .; + } > 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/test/panic_function_test/panic_function_test.c b/test/panic_function_test/panic_function_test.c index 26c77265c..7e4457557 100644 --- a/test/panic_function_test/panic_function_test.c +++ b/test/panic_function_test/panic_function_test.c @@ -1,3 +1,9 @@ +/* +* Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + #include #include diff --git a/test/pico_low_power_test/BUILD.bazel b/test/pico_low_power_test/BUILD.bazel new file mode 100644 index 000000000..7483faf5a --- /dev/null +++ b/test/pico_low_power_test/BUILD.bazel @@ -0,0 +1,56 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("//bazel:defs.bzl", "compatible_with_rp2") + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "low_power_test_common", + hdrs = ["low_power_test_common.h"], + includes = ["."], + deps = [ + "//src/rp2_common/pico_stdlib", + "//src/rp2_common/pico_low_power", + "//src/rp2_common/pico_status_led", + ], +) + +cc_binary( + name = "low_power_test_timers", + testonly = True, + srcs = ["low_power_test_timers.c"], + target_compatible_with = compatible_with_rp2(), + deps = [ + "low_power_test_common", + ], +) + +cc_binary( + name = "low_power_test_gpio", + testonly = True, + srcs = ["low_power_test_gpio.c"], + target_compatible_with = compatible_with_rp2(), + deps = [ + "low_power_test_common", + ], +) + +cc_binary( + name = "low_power_test_simple", + testonly = True, + srcs = ["low_power_test_simple.c"], + target_compatible_with = compatible_with_rp2(), + deps = [ + "low_power_test_common", + ], +) + +cc_binary( + name = "external_sleep_timer", + testonly = True, + srcs = ["external_sleep_timer.c"], + target_compatible_with = compatible_with_rp2(), + deps = [ + "low_power_test_common", + ], +) diff --git a/test/pico_low_power_test/CMakeLists.txt b/test/pico_low_power_test/CMakeLists.txt new file mode 100644 index 000000000..452c64e1f --- /dev/null +++ b/test/pico_low_power_test/CMakeLists.txt @@ -0,0 +1,148 @@ +add_library(low_power_test_common INTERFACE) +target_link_libraries(low_power_test_common INTERFACE pico_stdlib pico_low_power pico_status_led) +target_include_directories(low_power_test_common INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +add_executable(low_power_test_timers + low_power_test_timers.c + ) +target_link_libraries(low_power_test_timers low_power_test_common) +set_target_properties(low_power_test_timers PROPERTIES PICO_TEST_TIMEOUT 35) +set_target_properties(low_power_test_timers PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") +set_target_properties(low_power_test_timers PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) +# create map/bin/hex file etc. +pico_add_extra_outputs(low_power_test_timers) + + +add_executable(low_power_test_timers_usb + low_power_test_timers.c + ) +target_link_libraries(low_power_test_timers_usb low_power_test_common) +pico_enable_stdio_usb(low_power_test_timers_usb 1) +set_target_properties(low_power_test_timers_usb PROPERTIES PICO_TEST_TIMEOUT 35) +set_target_properties(low_power_test_timers_usb PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") +set_target_properties(low_power_test_timers_usb PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) +# create map/bin/hex file etc. +pico_add_extra_outputs(low_power_test_timers_usb) + + +if (NOT PICO_RP2040) + if (NOT PICO_RISCV) + add_executable(low_power_test_timers_encrypted + low_power_test_timers.c + ) + target_link_libraries(low_power_test_timers_encrypted low_power_test_common) + pico_set_binary_type(low_power_test_timers_encrypted no_flash) + pico_package_uf2_output(low_power_test_timers_encrypted) + pico_sign_binary(low_power_test_timers_encrypted ${PICO_SDK_PATH}/tools/example_keys/private.pem) + pico_encrypt_binary(low_power_test_timers_encrypted ${PICO_SDK_PATH}/tools/example_keys/privateaes.bin ${PICO_SDK_PATH}/tools/example_keys/ivsalt.bin EMBED NO_CLEAR) + set_target_properties(low_power_test_timers_encrypted PROPERTIES PICO_TEST_TIMEOUT 35) + set_target_properties(low_power_test_timers_encrypted PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") + set_target_properties(low_power_test_timers_encrypted PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) + # create map/bin/hex file etc. + pico_add_extra_outputs(low_power_test_timers_encrypted) + endif() + + + add_executable(low_power_test_timers_sram0 + low_power_test_timers.c + ) + target_link_libraries(low_power_test_timers_sram0 low_power_test_common) + pico_set_persistent_data_loc(low_power_test_timers_sram0 0x20020000) + # create map/bin/hex file etc. + set_target_properties(low_power_test_timers_sram0 PROPERTIES PICO_TEST_TIMEOUT 35) + set_target_properties(low_power_test_timers_sram0 PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") + set_target_properties(low_power_test_timers_sram0 PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) + pico_add_extra_outputs(low_power_test_timers_sram0) + + + add_executable(low_power_test_timers_sram1 + low_power_test_timers.c + ) + target_link_libraries(low_power_test_timers_sram1 low_power_test_common) + pico_set_persistent_data_loc(low_power_test_timers_sram1 0x20040000) + # create map/bin/hex file etc. + set_target_properties(low_power_test_timers_sram1 PROPERTIES PICO_TEST_TIMEOUT 35) + set_target_properties(low_power_test_timers_sram1 PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") + set_target_properties(low_power_test_timers_sram1 PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) + pico_add_extra_outputs(low_power_test_timers_sram1) + + + add_executable(low_power_test_timers_xip_sram + low_power_test_timers.c + ) + target_link_libraries(low_power_test_timers_xip_sram low_power_test_common) + pico_set_persistent_data_loc(low_power_test_timers_xip_sram 0x13ffc000) + # create map/bin/hex file etc. + set_target_properties(low_power_test_timers_xip_sram PROPERTIES PICO_TEST_TIMEOUT 35) + set_target_properties(low_power_test_timers_xip_sram PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") + set_target_properties(low_power_test_timers_xip_sram PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) + pico_add_extra_outputs(low_power_test_timers_xip_sram) + + + add_executable(low_power_test_timers_xip_sram_no_flash + low_power_test_timers.c + ) + target_link_libraries(low_power_test_timers_xip_sram_no_flash low_power_test_common) + pico_set_persistent_data_loc(low_power_test_timers_xip_sram_no_flash 0x13ffc000) + pico_set_binary_type(low_power_test_timers_xip_sram_no_flash no_flash) + pico_package_uf2_output(low_power_test_timers_xip_sram_no_flash) + # create map/bin/hex file etc. + set_target_properties(low_power_test_timers_xip_sram_no_flash PROPERTIES PICO_TEST_TIMEOUT 35) + set_target_properties(low_power_test_timers_xip_sram_no_flash PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") + set_target_properties(low_power_test_timers_xip_sram_no_flash PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) + pico_add_extra_outputs(low_power_test_timers_xip_sram_no_flash) +endif() + + +add_executable(external_sleep_timer + external_sleep_timer.c + ) +target_link_libraries(external_sleep_timer low_power_test_common) +# This is a buddy program, not a test +set_target_properties(external_sleep_timer PROPERTIES PICO_TEST_SKIP_IN_CI TRUE) +# create map/bin/hex file etc. +pico_add_extra_outputs(external_sleep_timer) + + +add_executable(low_power_test_gpio + low_power_test_gpio.c + ) +target_link_libraries(low_power_test_gpio low_power_test_common) +set_target_properties(low_power_test_gpio PROPERTIES PICO_TEST_TIMEOUT 25) +set_target_properties(low_power_test_gpio PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") +set_target_properties(low_power_test_gpio PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) +# create map/bin/hex file etc. +pico_add_extra_outputs(low_power_test_gpio) + + +add_executable(low_power_test_gpio_usb + low_power_test_gpio.c + ) +target_link_libraries(low_power_test_gpio_usb low_power_test_common) +pico_enable_stdio_usb(low_power_test_gpio_usb 1) +set_target_properties(low_power_test_gpio_usb PROPERTIES PICO_TEST_TIMEOUT 25) +set_target_properties(low_power_test_gpio_usb PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") +set_target_properties(low_power_test_gpio_usb PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) +# create map/bin/hex file etc. +pico_add_extra_outputs(low_power_test_gpio_usb) + + +add_executable(low_power_test_simple + low_power_test_simple.c + ) +target_link_libraries(low_power_test_simple low_power_test_common) +set_target_properties(low_power_test_simple PROPERTIES PICO_TEST_TIMEOUT 25) +set_target_properties(low_power_test_simple PROPERTIES PICO_TEST_FAILURE_STRING "ERROR:") +set_target_properties(low_power_test_simple PROPERTIES PICO_TEST_BUDDY_FILE external_sleep_timer.elf) +# create map/bin/hex file etc. +pico_add_extra_outputs(low_power_test_simple) + + +add_custom_target(low_power_tests) +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(low_power_tests ${target}) + endif() +endforeach() diff --git a/test/pico_low_power_test/external_sleep_timer.c b/test/pico_low_power_test/external_sleep_timer.c new file mode 100644 index 000000000..fe3c655e1 --- /dev/null +++ b/test/pico_low_power_test/external_sleep_timer.c @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/stdlib.h" +#include "pico/sync.h" +#include "hardware/clocks.h" + +#include "low_power_test_common.h" + +#if PICO_RP2040 +#define TOLERANCE_MS 1000 // The resolution of the AON timer is 1s on RP2040 +#elif PICO_RISCV +#define TOLERANCE_MS 200 // Can take longer to wake up that Arm +#else +#define TOLERANCE_MS 100 +#endif +#define MIN_SLEEP_TIME_MS SLEEP_TIME_MS - TOLERANCE_MS +#define MAX_SLEEP_TIME_MS SLEEP_TIME_MS + TOLERANCE_MS + +uint32_t wakeup_time_ms = 0; +uint32_t sleep_time_ms = 0; +bool good_sleep_done = false; + +int64_t wake_up_gpio(__unused alarm_id_t id, __unused void *param) { + gpio_put(WAKE_UP_PIN, 0); + return 0; +} + +static alarm_id_t wake_up_alarm_id; + +void gpio_callback(uint gpio, uint32_t events) { + if (events & GPIO_IRQ_EDGE_RISE) { + wakeup_time_ms = to_ms_since_boot(get_absolute_time()); + printf("Woke up at %dms\n", wakeup_time_ms); + gpio_put(WAKE_UP_PIN, 1); + } else if (events & GPIO_IRQ_EDGE_FALL) { + sleep_time_ms = to_ms_since_boot(get_absolute_time()); + printf("Went to sleep at %dms\n", sleep_time_ms); + wake_up_alarm_id = add_alarm_in_ms(SLEEP_TIME_MS, wake_up_gpio, NULL, false); + } + if (wakeup_time_ms > sleep_time_ms) { + uint32_t diff = wakeup_time_ms - sleep_time_ms; + if (good_sleep_done && (diff < MIN_SLEEP_TIME_MS || diff > MAX_SLEEP_TIME_MS)) { + printf("ERROR: Was asleep for %dms, expected between %dms and %dms\n", diff, MIN_SLEEP_TIME_MS, MAX_SLEEP_TIME_MS); + } else if (diff >= MIN_SLEEP_TIME_MS && diff <= MAX_SLEEP_TIME_MS){ + printf("Was asleep for %dms\n", diff); + good_sleep_done = true; + } + } +} + + +int main() { + stdio_init_all(); + +#if PICO_RP2040 + printf("Outputting RTC clock to GPIO %d\n", RTC_GPIO_OUT); + clock_gpio_init(RTC_GPIO_OUT, CLOCKS_CLK_GPOUT3_CTRL_AUXSRC_VALUE_CLK_RTC, 1); +#endif + + printf("Monitoring for sleep events on GPIO %d\n", SLEEP_MONITOR_PIN); + gpio_init(SLEEP_MONITOR_PIN); + gpio_pull_up(SLEEP_MONITOR_PIN); + gpio_set_irq_enabled_with_callback(SLEEP_MONITOR_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback); + + printf("Waking up device on GPIO %d\n", WAKE_UP_PIN); + gpio_init(WAKE_UP_PIN); + gpio_set_dir(WAKE_UP_PIN, GPIO_OUT); + gpio_put(WAKE_UP_PIN, 1); + + while (true) __wfi(); + + return 0; +} \ No newline at end of file diff --git a/test/pico_low_power_test/low_power_test_common.h b/test/pico_low_power_test/low_power_test_common.h new file mode 100644 index 000000000..d09e5c077 --- /dev/null +++ b/test/pico_low_power_test/low_power_test_common.h @@ -0,0 +1,41 @@ +#ifndef LOW_POWER_TEST_COMMON_H +#define LOW_POWER_TEST_COMMON_H + +#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 + +// Use default I2C pins as that should be connected to the other board's I2C pins +#define SLEEP_MONITOR_PIN PICO_DEFAULT_I2C_SDA_PIN +#define WAKE_UP_PIN PICO_DEFAULT_I2C_SCL_PIN + +// On RP2040 this must be a GPIO that supports clock input, see the GPIO function table in the datasheet. +#define RTC_GPIO_IN 22 + +// On RP2040 this must be a GPIO that supports clock output, see the GPIO function table in the datasheet. +#define RTC_GPIO_OUT 21 + + +static inline void init_external_gpios(void) { + gpio_init(SLEEP_MONITOR_PIN); + gpio_set_dir(SLEEP_MONITOR_PIN, GPIO_OUT); + gpio_put(SLEEP_MONITOR_PIN, 1); + gpio_init(WAKE_UP_PIN); + gpio_pull_up(WAKE_UP_PIN); +} + +#if HAS_POWMAN_TIMER +static inline void init_powman_ext_ctrl(void) { + powman_hw->ext_ctrl[0] = POWMAN_EXT_CTRL0_LP_EXIT_STATE_BITS | POWMAN_EXT_CTRL0_INIT_STATE_BITS | SLEEP_MONITOR_PIN; + hw_set_bits(&powman_hw->ext_ctrl[0], POWMAN_EXT_CTRL0_INIT_BITS); +} +#endif + +#endif // LOW_POWER_TEST_COMMON_H \ No newline at end of file diff --git a/test/pico_low_power_test/low_power_test_gpio.c b/test/pico_low_power_test/low_power_test_gpio.c new file mode 100644 index 000000000..080deec80 --- /dev/null +++ b/test/pico_low_power_test/low_power_test_gpio.c @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "low_power_test_common.h" + +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"); + init_external_gpios(); + // 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); + + int ret; + + printf("Going to sleep until GPIO wakeup\n"); + + gpio_put(SLEEP_MONITOR_PIN, 0); + low_power_sleep_until_gpio_pin_state(WAKE_UP_PIN, true, false, NULL, true); + gpio_put(SLEEP_MONITOR_PIN, 1); + 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 + + gpio_put(SLEEP_MONITOR_PIN, 0); + low_power_sleep_until_gpio_pin_state(WAKE_UP_PIN, true, false, &keep_enabled, false); + gpio_put(SLEEP_MONITOR_PIN, 1); + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + // Skip this test as it requires stdin characters + // printf("Going to sleep until any wakeup (expecting stdin characters)\n"); + + // gpio_put(SLEEP_MONITOR_PIN, 0); + // low_power_sleep_until_irq(NULL); + // gpio_put(SLEEP_MONITOR_PIN, 1); + // printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + // busy_wait_ms(SLEEP_TIME_MS); + + low_power_start_aon_timer_at_time_ms(0); + + printf("Going DORMANT until GPIO wakeup\n"); + + gpio_put(SLEEP_MONITOR_PIN, 0); + low_power_dormant_until_gpio_pin_state(WAKE_UP_PIN, true, false, DORMANT_CLOCK_SOURCE_ROSC, NULL); + gpio_put(SLEEP_MONITOR_PIN, 1); + 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"); + + // Setup ext_ctrl0 to output on the SLEEP_MONITOR_PIN + init_powman_ext_ctrl(); + + gpio_put(SLEEP_MONITOR_PIN, 0); + ret = low_power_pstate_until_gpio_pin_state(WAKE_UP_PIN, true, false, NULL, pstate_resume_func); + + printf("ERROR: %d returned by low_power_pstate_until_gpio_pin_state\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("PASSED\n"); + + return 0; +} diff --git a/test/pico_low_power_test/low_power_test_simple.c b/test/pico_low_power_test/low_power_test_simple.c new file mode 100644 index 000000000..4599c65c9 --- /dev/null +++ b/test/pico_low_power_test/low_power_test_simple.c @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "low_power_test_common.h" + +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(); + + init_external_gpios(); + + low_power_set_external_clock_source(DORMANT_CLOCK_HZ_DEFAULT, RTC_GPIO_IN); + + int ret; + static int __persistent_data(num_runs); + for (num_runs++; num_runs < 5; num_runs++) { // start at 1 to prove the persistent data is working + printf("Run %d\n", num_runs); + + printf("Going to sleep for %dms\n", SLEEP_TIME_MS); + gpio_put(SLEEP_MONITOR_PIN, 0); + ret = low_power_sleep_for_ms(SLEEP_TIME_MS, NULL, true); + gpio_put(SLEEP_MONITOR_PIN, 1); + if (ret != PICO_OK) { + printf("ERROR: low_power_sleep_for_ms returned %d\n", ret); + } else { + printf("Woken up\n"); + } + + printf("Going dormant for %dms\n", SLEEP_TIME_MS); + gpio_put(SLEEP_MONITOR_PIN, 0); + ret = low_power_dormant_for_ms(SLEEP_TIME_MS, DORMANT_CLOCK_SOURCE_DEFAULT, NULL); + gpio_put(SLEEP_MONITOR_PIN, 1); + if (ret != PICO_OK) { + printf("ERROR: low_power_dormant_for_ms returned %d\n", ret); + } else { + printf("Woken up\n"); + } + +#if HAS_POWMAN_TIMER + printf("Going to Pstate for %dms\n", SLEEP_TIME_MS); + // Setup ext_ctrl0 to output on the SLEEP_MONITOR_PIN + init_powman_ext_ctrl(); + gpio_put(SLEEP_MONITOR_PIN, 0); + ret = low_power_pstate_for_ms(SLEEP_TIME_MS, NULL, NULL); + if (ret != PICO_OK) { + printf("%d ERROR: low_power_pstate_for_ms returned\n", ret); + } else { + printf("ERROR: Woken up from Pstate\n"); + } +#endif + } + + printf("PASSED\n"); + return 0; +} diff --git a/test/pico_low_power_test/low_power_test_timers.c b/test/pico_low_power_test/low_power_test_timers.c new file mode 100644 index 000000000..b82804305 --- /dev/null +++ b/test/pico_low_power_test/low_power_test_timers.c @@ -0,0 +1,398 @@ +/** + * Copyright (c) 2026 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "low_power_test_common.h" + +// Set to 1 to continue after an error, 0 to exit +#if 0 +#define EXIT_TEST +#else +#define EXIT_TEST return -1 +#endif + +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"); + + init_external_gpios(); + + // 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; + int ret; + + low_power_set_external_clock_source(DORMANT_CLOCK_HZ_DEFAULT, RTC_GPIO_IN); + + + + // exclusive sleep + printf("Going to sleep for %d seconds via TIMER\n", SLEEP_TIME_S); + + gpio_put(SLEEP_MONITOR_PIN, 0); + start_time = get_absolute_time(); + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + ret = low_power_sleep_until_timer(timer_hw, wakeup_time, NULL, true); + if (ret != PICO_OK) { + printf("ERROR: %d returned by low_power_sleep_until_timer\n", ret); + EXIT_TEST; + } + gpio_put(SLEEP_MONITOR_PIN, 1); + 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"); + EXIT_TEST; + } + 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); + + gpio_put(SLEEP_MONITOR_PIN, 0); + start_time = get_absolute_time(); + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + ret = low_power_sleep_until_timer(timer_hw, wakeup_time, NULL, false); + if (ret != PICO_OK) { + printf("ERROR: %d returned by low_power_sleep_until_timer\n", ret); + EXIT_TEST; + } + gpio_put(SLEEP_MONITOR_PIN, 1); + 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"); + EXIT_TEST; + } + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + + + // start the AON timer + low_power_start_aon_timer_at_time_ms(0); + printf("AON timer started @%dms\n", to_ms_since_boot(aon_timer_get_absolute_time())); + + + + // exclusive sleep using the AON timer + printf("Going to sleep for %d seconds via AON timer\n", SLEEP_TIME_S); + + gpio_put(SLEEP_MONITOR_PIN, 0); + start_time = aon_timer_get_absolute_time(); + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + ret = low_power_sleep_until_aon_timer(wakeup_time, NULL, true); + if (ret != PICO_OK) { + printf("ERROR: %d returned by low_power_sleep_until_aon_timer\n", ret); + EXIT_TEST; + } + gpio_put(SLEEP_MONITOR_PIN, 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("ERROR: Woke up too soon\n"); + EXIT_TEST; + } + printf("Doing %d second pause to prove timer running\n", SLEEP_TIME_S); + busy_wait_ms(SLEEP_TIME_MS); + + + + // non-exclusive sleep using the AON timer + printf("Going to non-exclusive sleep for %d seconds via AON timer\n", SLEEP_TIME_S); + + // leave the system 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 + + gpio_put(SLEEP_MONITOR_PIN, 0); + start_time = aon_timer_get_absolute_time(); + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + ret = low_power_sleep_until_aon_timer(wakeup_time, &keep_enabled, false); + if (ret != PICO_OK) { + printf("ERROR: %d returned by low_power_sleep_until_aon_timer\n", ret); + EXIT_TEST; + } + gpio_put(SLEEP_MONITOR_PIN, 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("ERROR: Woke up too soon\n"); + EXIT_TEST; + } + 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); + + gpio_put(SLEEP_MONITOR_PIN, 0); + start_time = aon_timer_get_absolute_time(); + absolute_time_t system_time_before = get_absolute_time(); + wakeup_time = delayed_by_ms(start_time, SLEEP_TIME_MS); + ret = low_power_dormant_until_aon_timer(wakeup_time, DORMANT_CLOCK_SOURCE_DEFAULT, NULL); + if (ret != PICO_OK) { + printf("ERROR: %d returned by low_power_dormant_until_aon_timer\n", ret); + #if PICO_RP2040 + if (ret == PICO_ERROR_PRECONDITION_NOT_MET) { + printf("ERROR: RTC clock source is not running - connect a device running external_sleep_timer to GPIO %d\n", RTC_GPIO_IN); + } + #endif + EXIT_TEST; + } + gpio_put(SLEEP_MONITOR_PIN, 1); + // check the system timer was stopped while dormant + diff = absolute_time_diff_us(system_time_before, get_absolute_time()); + if (diff > 200 * 1000 // 200ms + #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: diff %lldus\n", diff); + 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); + + // Setup ext_ctrl0 to output on the SLEEP_MONITOR_PIN + init_powman_ext_ctrl(); + + if (my_number != 12345) { + printf("ERROR: my_number is %d not 12345 - initialisation issue?\n", my_number); + EXIT_TEST; + } + my_number = 67890; + + gpio_put(SLEEP_MONITOR_PIN, 0); + 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); + + if (ret != PICO_OK) { + printf("ERROR: %d returned by low_power_pstate_until_aon_timer\n", ret); + EXIT_TEST; + } + 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)); + EXIT_TEST; + } + + if (my_number != 67890) { + printf("ERROR: my_number is %d not 67890 - SRAM has been re-loaded\n", my_number); + EXIT_TEST; + } 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); + + gpio_put(SLEEP_MONITOR_PIN, 0); + 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); + + if (ret != PICO_OK) { + printf("ERROR: %d returned by low_power_pstate_until_aon_timer\n", ret); + EXIT_TEST; + } + 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)); + EXIT_TEST; + } + + if (my_number != 34567) { + printf("ERROR: my_number is %d not 34567 - SRAM has not been re-loaded\n", my_number); + EXIT_TEST; + } 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]); + EXIT_TEST; + } +#endif + + printf("PASSED\n"); + + return 0; +} \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 5a6795ef2..85553bb7c 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 @@ -101,6 +107,12 @@ define_property(TARGET BRIEF_DOCS "Ensure a load map is added" FULL_DOCS "Ensure a load map is added" ) +define_property(TARGET + PROPERTY PICO_ALLOW_EXAMPLE_KEYS + INHERITED + BRIEF_DOCS "Allow use of example keys" + FULL_DOCS "Allow use of example keys" +) # Check pioasm is installed, or build it if not installed function(pico_init_pioasm) @@ -209,6 +221,7 @@ function(picotool_check_default_keys TARGET) picotool_compare_keys(${TARGET} ${picotool_sigfile} private.pem "signing") get_target_property(picotool_aesfile ${TARGET} PICOTOOL_AESFILE) picotool_compare_keys(${TARGET} ${picotool_aesfile} privateaes.bin "encryption") + picotool_compare_keys(${TARGET} ${picotool_aesfile} privateaes_old.bin "encryption") get_target_property(picotool_enc_sigfile ${TARGET} PICOTOOL_ENC_SIGFILE) picotool_compare_keys(${TARGET} ${picotool_enc_sigfile} private.pem "encrypted signing") endfunction() @@ -480,7 +493,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 +523,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 +557,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} @@ -635,6 +655,10 @@ function(picotool_postprocess_binary TARGET) PICOTOOL_PROCESSING_CONFIGURED true ) + if (NOT DEFINED PICO_ALLOW_EXAMPLE_KEYS) + get_target_property(PICO_ALLOW_EXAMPLE_KEYS ${TARGET} PICO_ALLOW_EXAMPLE_KEYS) + endif() + # PICO_CMAKE_CONFIG: PICO_ALLOW_EXAMPLE_KEYS, Don't raise a warning when using default signing/encryption keys, type=bool, default=0, group=build if (NOT PICO_ALLOW_EXAMPLE_KEYS) picotool_check_default_keys(${TARGET}) @@ -709,9 +733,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 (enc_no_clear) + list(APPEND picotool_encrypt_args "--no-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..7aa18173b 100755 --- a/tools/bazel_build.py +++ b/tools/bazel_build.py @@ -52,6 +52,10 @@ "//test/pico_sha256_test:pico_sha256_test", "//test/pico_stdio_test:pico_stdio_test", "//test/pico_time_test:pico_time_test", + "//test/pico_low_power_test:low_power_test_timers", + "//test/pico_low_power_test:low_power_test_gpio", + "//test/pico_low_power_test:low_power_test_simple", + "//test/pico_low_power_test:external_sleep_timer", "//test/pico_async_context_test:pico_async_context_test", # Pretty much only Picotool and pioasm build on Windows. @@ -214,6 +218,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/example_keys/privateaes.bin b/tools/example_keys/privateaes.bin index 21a47756d..533d79266 100644 Binary files a/tools/example_keys/privateaes.bin and b/tools/example_keys/privateaes.bin differ diff --git a/tools/example_keys/privateaes_old.bin b/tools/example_keys/privateaes_old.bin new file mode 100644 index 000000000..21a47756d Binary files /dev/null and b/tools/example_keys/privateaes_old.bin differ 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'), }